From a9c8a2496d54668d2d46133ff0ef1787cd7b80d6 Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Wed, 10 Feb 2010 11:03:35 +0200 Subject: dialyzer: New version for the R13B04 release --- lib/dialyzer/src/Makefile | 2 + lib/dialyzer/src/dialyzer.app.src | 5 +- lib/dialyzer/src/dialyzer.erl | 61 +++-- lib/dialyzer/src/dialyzer.hrl | 5 +- lib/dialyzer/src/dialyzer_analysis_callgraph.erl | 51 +++- lib/dialyzer/src/dialyzer_behaviours.erl | 324 +++++++++++++++++++++++ lib/dialyzer/src/dialyzer_callgraph.erl | 21 +- lib/dialyzer/src/dialyzer_cl.erl | 50 +++- lib/dialyzer/src/dialyzer_cl_parse.erl | 46 +++- lib/dialyzer/src/dialyzer_codeserver.erl | 77 +++--- lib/dialyzer/src/dialyzer_contracts.erl | 36 ++- lib/dialyzer/src/dialyzer_dataflow.erl | 181 ++++++++----- lib/dialyzer/src/dialyzer_dep.erl | 2 +- lib/dialyzer/src/dialyzer_gui_wx.erl | 61 ++--- lib/dialyzer/src/dialyzer_options.erl | 2 + lib/dialyzer/src/dialyzer_plt.erl | 138 +++++----- lib/dialyzer/src/dialyzer_races.erl | 165 ++++++++---- lib/dialyzer/src/dialyzer_succ_typings.erl | 23 +- lib/dialyzer/src/dialyzer_typesig.erl | 64 +++-- lib/dialyzer/src/dialyzer_utils.erl | 128 ++++++--- 20 files changed, 1068 insertions(+), 374 deletions(-) create mode 100644 lib/dialyzer/src/dialyzer_behaviours.erl (limited to 'lib/dialyzer/src') diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile index ffdc0c6dcd..2521a866f3 100644 --- a/lib/dialyzer/src/Makefile +++ b/lib/dialyzer/src/Makefile @@ -48,6 +48,7 @@ DIALYZER_DIR = $(ERL_TOP)/lib/dialyzer MODULES = \ dialyzer \ dialyzer_analysis_callgraph \ + dialyzer_behaviours \ dialyzer_callgraph \ dialyzer_cl \ dialyzer_cl_parse \ @@ -128,6 +129,7 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk $(EBIN)/dialyzer.beam: dialyzer.hrl $(EBIN)/dialyzer_analysis_callgraph.beam: dialyzer.hrl $(EBIN)/dialyzer_callgraph.beam: dialyzer.hrl +$(EBIN)/dialyzer_behaviours.beam: dialyzer.hrl $(EBIN)/dialyzer_cl.beam: dialyzer.hrl ../../kernel/include/file.hrl $(EBIN)/dialyzer_cl_parse.beam: dialyzer.hrl $(EBIN)/dialyzer_codeserver.beam: dialyzer.hrl diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index c1d109812c..bd8b79bf21 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -23,6 +23,7 @@ {vsn, "%VSN%"}, {modules, [dialyzer, dialyzer_analysis_callgraph, + dialyzer_behaviours, dialyzer_callgraph, dialyzer_cl, dialyzer_cl_parse, @@ -30,7 +31,9 @@ dialyzer_contracts, dialyzer_dataflow, dialyzer_dep, + dialyzer_explanation, dialyzer_gui, + dialyzer_gui_wx, dialyzer_options, dialyzer_plt, dialyzer_races, @@ -38,5 +41,5 @@ dialyzer_typesig, dialyzer_utils]}, {registered, []}, - {applications, [compiler, gs, hipe, kernel, stdlib]}, + {applications, [compiler, gs, hipe, kernel, stdlib, wx]}, {env, []}]}. diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index c1897ed892..95955db7e9 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -379,7 +379,7 @@ message_to_string({spec_missing_fun, [M, F, A]}) -> [M, F, A]); %%----- Warnings for opaque type violations ------------------- message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) -> - io_lib:format("The call ~w:~w~s contains ~s when ~s\n", + io_lib:format("The call ~w:~w~s contains ~s argument when ~s\n", [M, F, Args, form_positions(ArgNs), form_expected(ExpArgs)]); message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) -> io_lib:format("The call ~w:~w~s does not have ~s\n", @@ -403,8 +403,25 @@ 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]); %%----- 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]). - + io_lib:format("The call ~w:~w~s ~s\n", [M, F, Args, Reason]); +%%----- Warnings for behaviour errors -------------------- +message_to_string({callback_type_mismatch, [B, F, A, O]}) -> + io_lib:format("The inferred return type of the ~w/~w callback includes the" + " type ~s which is not a valid return for the ~w behaviour\n", + [F, A, erl_types:t_to_string(O), B]); +message_to_string({callback_arg_type_mismatch, [B, F, A, N, O]}) -> + io_lib:format("The inferred type of the ~s argument of ~w/~w callback" + " includes the type ~s which is not valid for the ~w behaviour" + "\n", [ordinal(N), F, A, erl_types:t_to_string(O), B]); +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({invalid_spec, [B, F, A, R]}) -> + io_lib:format("The spec for the ~w:~w/~w callback is not correct: ~s\n", + [B, F, A, R]); +message_to_string({spec_missing, [B, F, A]}) -> + io_lib:format("Type info about ~w:~w/~w callback is not available\n", + [B, F, A]). %%----------------------------------------------------------------------------- %% Auxiliary functions below @@ -421,8 +438,8 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, io_lib:format("will never return since the success typing arguments" " are ~s\n", [SigArgs]); false -> - io_lib:format("will never return since it differs in argument" - " ~s from the success typing arguments: ~s\n", + io_lib:format("will never return since it differs in the ~s argument" + " from the success typing arguments: ~s\n", [PositionString, SigArgs]) end; only_contract -> @@ -431,7 +448,7 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, %% We do not know which arguments caused the failure io_lib:format("breaks the contract ~s\n", [Contract]); false -> - io_lib:format("breaks the contract ~s in argument ~s\n", + io_lib:format("breaks the contract ~s in the ~s argument\n", [Contract, PositionString]) end; both -> @@ -441,22 +458,26 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, form_positions(ArgNs) -> case ArgNs of - [_] -> "an opaque term in "; - [_,_|_] -> "opaque terms in " - end ++ form_position_string(ArgNs). + [_] -> "an opaque term as "; + [_,_|_] -> "opaque terms as " + end ++ form_position_string(ArgNs) ++ + case ArgNs of + [_] -> " argument"; + [_,_|_] -> " arguments" + end. %% We know which positions N are to blame; %% the list of triples will never be empty. form_expected_without_opaque([{N, T, TStr}]) -> case erl_types:t_is_opaque(T) of true -> - io_lib:format("an opaque term of type ~s in ", [TStr]); + io_lib:format("an opaque term of type ~s as ", [TStr]); false -> - io_lib:format("a term of type ~s (with opaque subterms) in ", [TStr]) - end ++ form_position_string([N]); + io_lib:format("a term of type ~s (with opaque subterms) as ", [TStr]) + end ++ form_position_string([N]) ++ " argument"; form_expected_without_opaque(ExpectedTriples) -> %% TODO: can do much better here {ArgNs, _Ts, _TStrs} = lists:unzip3(ExpectedTriples), - "opaque terms in " ++ form_position_string(ArgNs). + "opaque terms as " ++ form_position_string(ArgNs) ++ " arguments". form_expected(ExpectedArgs) -> case ExpectedArgs of @@ -472,9 +493,15 @@ form_expected(ExpectedArgs) -> form_position_string(ArgNs) -> case ArgNs of [] -> ""; - [N1] -> io_lib:format("position ~w", [N1]); + [N1] -> ordinal(N1); [_,_|_] -> - " and"++ArgString = lists:flatten([io_lib:format(" and ~w", [N]) - || N <- ArgNs]), - "positions" ++ ArgString + [Last|Prevs] = lists:reverse(ArgNs), + ", " ++ Head = lists:flatten([io_lib:format(", ~s",[ordinal(N)]) || + N <- lists:reverse(Prevs)]), + Head ++ " and " ++ ordinal(Last) end. + +ordinal(1) -> "1st"; +ordinal(2) -> "2nd"; +ordinal(3) -> "3rd"; +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 f0f9bd25d7..d50b8720f8 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -55,6 +55,7 @@ -define(WARN_CALLGRAPH, warn_callgraph). -define(WARN_UNMATCHED_RETURN, warn_umatched_return). -define(WARN_RACE_CONDITION, warn_race_condition). +-define(WARN_BEHAVIOUR,warn_behaviour). %% %% The following type has double role: @@ -68,7 +69,8 @@ | ?WARN_CONTRACT_TYPES | ?WARN_CONTRACT_SYNTAX | ?WARN_CONTRACT_NOT_EQUAL | ?WARN_CONTRACT_SUBTYPE | ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH - | ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION. + | ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION + | ?WARN_BEHAVIOUR. %% %% This is the representation of each warning as they will be returned @@ -118,6 +120,7 @@ plt :: dialyzer_plt:plt(), use_contracts = true :: boolean(), race_detection = false :: boolean(), + behaviours_chk = false :: boolean(), callgraph_file = "" :: file:filename()}). -record(options, {files = [] :: [file:filename()], diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 97d63a1f14..db62dcebac 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -43,7 +43,8 @@ parent :: pid(), plt :: dialyzer_plt:plt(), start_from = byte_code :: start_from(), - use_contracts = true :: boolean() + use_contracts = true :: boolean(), + behaviours = {false,[]} :: {boolean(),[atom()]} }). -record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}). @@ -56,7 +57,9 @@ start(Parent, LegalWarnings, Analysis) -> RacesOn = ordsets:is_element(?WARN_RACE_CONDITION, LegalWarnings), - Analysis0 = Analysis#analysis{race_detection = RacesOn}, + BehavOn = ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings), + Analysis0 = Analysis#analysis{race_detection = RacesOn, + behaviours_chk = BehavOn}, Analysis1 = expand_files(Analysis0), Analysis2 = run_analysis(Analysis1), State = #server_state{parent = Parent, legal_warnings = LegalWarnings}, @@ -93,6 +96,9 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State, end; {AnalPid, ext_calls, NewExtCalls} -> loop(State, Analysis, NewExtCalls); + {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); @@ -116,7 +122,9 @@ analysis_start(Parent, Analysis) -> plt = Plt, parent = Parent, start_from = Analysis#analysis.start_from, - use_contracts = Analysis#analysis.use_contracts + use_contracts = Analysis#analysis.use_contracts, + behaviours = {Analysis#analysis.behaviours_chk, + []} }, Files = ordsets:from_list(Analysis#analysis.files), {Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State), @@ -167,11 +175,13 @@ analyze_callgraph(Callgraph, State) -> State#analysis_state{plt = NewPlt}; succ_typings -> NoWarn = State#analysis_state.no_warn_unused, + {BehavioursChk, _Known} = State#analysis_state.behaviours, DocPlt = State#analysis_state.doc_plt, Callgraph1 = dialyzer_callgraph:finalize(Callgraph), {Warnings, NewPlt, NewDocPlt} = dialyzer_succ_typings:get_warnings(Callgraph1, Plt, DocPlt, - Codeserver, NoWarn, Parent), + Codeserver, NoWarn, Parent, + BehavioursChk), dialyzer_callgraph:delete(Callgraph1), send_warnings(State#analysis_state.parent, Warnings), State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} @@ -186,7 +196,9 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, include_dirs = Dirs, parent = Parent, use_contracts = UseContracts, - start_from = StartFrom} = State) -> + start_from = StartFrom, + behaviours = {BehChk, _} + } = State) -> send_log(Parent, "Reading files and computing callgraph... "), {T1, _} = statistics(runtime), Includes = [{i, D} || D <- Dirs], @@ -234,18 +246,37 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, {T2, _} = statistics(runtime), Msg1 = io_lib:format("done in ~.2f secs\nRemoving edges... ", [(T2-T1)/1000]), send_log(Parent, Msg1), - NewCallgraph2 = cleanup_callgraph(State, NewCServer, NewCallgraph1, Modules), + {KnownBehaviours, UnknownBehaviours} = + dialyzer_behaviours:get_behaviours(Modules, NewCServer), + if UnknownBehaviours =:= [] -> ok; + true -> send_unknown_behaviours(Parent, UnknownBehaviours) + end, + State1 = State#analysis_state{behaviours = {BehChk,KnownBehaviours}}, + NewCallgraph2 = cleanup_callgraph(State1, NewCServer, NewCallgraph1, Modules), {T3, _} = statistics(runtime), Msg2 = io_lib:format("done in ~.2f secs\n", [(T3-T2)/1000]), send_log(Parent, Msg2), {NewCallgraph2, sets:from_list(NoWarn), NewCServer}. cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, - codeserver = CodeServer}, + codeserver = CodeServer, + behaviours = {BehChk, KnownBehaviours} + }, CServer, Callgraph, Modules) -> ModuleDeps = dialyzer_callgraph:module_deps(Callgraph), send_mod_deps(Parent, ModuleDeps), {Callgraph1, ExtCalls} = dialyzer_callgraph:remove_external(Callgraph), + if BehChk -> + RelevantAPICalls = + dialyzer_behaviours:get_behaviour_apis(KnownBehaviours), + BehaviourAPICalls = [Call || {_From, To} = Call <- ExtCalls, + lists:member(To, RelevantAPICalls)], + Callgraph2 = + dialyzer_callgraph:put_behaviour_api_calls(BehaviourAPICalls, + Callgraph1); + true -> + Callgraph2 = Callgraph1 + end, ExtCalls1 = [Call || Call = {_From, To} <- ExtCalls, not dialyzer_plt:contains_mfa(InitPlt, To)], {BadCalls1, RealExtCalls} = @@ -268,7 +299,7 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, true -> send_ext_calls(Parent, lists:usort([To || {_From, To} <- RealExtCalls])) end, - Callgraph1. + Callgraph2. compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts) -> DefaultIncludes = default_includes(filename:dirname(File)), @@ -445,6 +476,10 @@ send_ext_calls(Parent, ExtCalls) -> Parent ! {self(), ext_calls, ExtCalls}, ok. +send_unknown_behaviours(Parent, UnknownBehaviours) -> + Parent ! {self(), unknown_behaviours, UnknownBehaviours}, + ok. + send_codeserver_plt(Parent, CServer, Plt ) -> Parent ! {self(), cserver, CServer, Plt}, ok. diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl new file mode 100644 index 0000000000..61cdcf27c0 --- /dev/null +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -0,0 +1,324 @@ +%% -*- erlang-indent-level: 2 -*- +%%----------------------------------------------------------------------- +%% %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/. +%% +%% 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_behaviours.erl +%%% Authors : Stavros Aronis +%%% Description : Tools for analyzing proper behaviour usage. +%%% +%%% Created : 28 Oct 2009 by Stavros Aronis +%%%------------------------------------------------------------------- +%%% NOTE: This module is currently experimental -- do NOT rely on it! +%%%------------------------------------------------------------------- + +-module(dialyzer_behaviours). + +-export([check_callbacks/4, get_behaviours/2, get_behaviour_apis/1, + translate_behaviour_api_call/5, translatable_behaviours/1, + translate_callgraph/3]). + +%%-------------------------------------------------------------------- + +-include("dialyzer.hrl"). + +%%-------------------------------------------------------------------- + +-record(state, {plt :: dialyzer_plt:plt(), + codeserver :: dialyzer_codeserver:codeserver(), + filename :: string(), + behlines :: [{atom(), number()}]}). + +-spec get_behaviours([module()], dialyzer_codeserver:codeserver()) -> + {[atom()], [atom()]}. + +get_behaviours(Modules, Codeserver) -> + get_behaviours(Modules, Codeserver, [], []). + +-spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], + dialyzer_plt:plt(), + dialyzer_codeserver:codeserver()) -> [dial_warning()]. + +check_callbacks(Module, Attrs, Plt, Codeserver) -> + {Behaviours, BehLines} = get_behaviours(Attrs), + case Behaviours of + [] -> []; + _ -> {_Var,Code} = + dialyzer_codeserver:lookup_mfa_code({Module,module_info,0}, + Codeserver), + File = get_file(cerl:get_ann(Code)), + State = #state{plt = Plt, codeserver = Codeserver, filename = File, + behlines = BehLines}, + Warnings = get_warnings(Module, Behaviours, State), + [add_tag_file_line(Module, W, State) || W <- Warnings] + end. + +-spec translatable_behaviours(cerl:c_module()) -> [{atom(),[_]}]. + +translatable_behaviours(Tree) -> + Attrs = cerl:module_attrs(Tree), + {Behaviours, _BehLines} = get_behaviours(Attrs), + [{B, Calls} || B <- Behaviours, (Calls = behaviour_api_calls(B)) =/= []]. + +-spec get_behaviour_apis([atom()]) -> [mfa()]. + +get_behaviour_apis(Behaviours) -> + get_behaviour_apis(Behaviours, []). + +-spec translate_behaviour_api_call(_, _, _, _, _) -> _. + +translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, []) -> + plain_call; +translate_behaviour_api_call({Module, Fun, Arity}, ArgTypes, Args, + CallbackModule, BehApiInfo) -> + case lists:keyfind(Module, 1, BehApiInfo) of + false -> plain_call; + {Module, Calls} -> + case lists:keyfind({Fun, Arity}, 1, Calls) of + false -> plain_call; + {{Fun, Arity}, {CFun, CArity, COrder}} -> + {{CallbackModule, CFun, CArity}, + [nth_or_0(N, ArgTypes, erl_types:t_any()) || N <-COrder], + [nth_or_0(N, Args, bypassed) || N <-COrder]} + end + end; +translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, _BehApiInfo) -> + plain_call. + +-spec translate_callgraph([{atom(), _}], atom(), dialyzer_callgraph:callgraph()) + -> dialyzer_callgraph:callgraph(). + +translate_callgraph([{Behaviour,_}|Behaviours], Module, Callgraph) -> + UsedCalls = [Call || {_From, {M, _F, _A}} = Call <- + dialyzer_callgraph:get_behaviour_api_calls(Callgraph), + M =:= Behaviour], + Calls = [{{Behaviour, API, Arity}, Callback} || + {{API, Arity}, Callback} <- behaviour_api_calls(Behaviour)], + DirectCalls = [{From, {Module, Fun, Arity}} || + {From, To} <- UsedCalls,{API, {Fun, Arity, _Ord}} <- Calls, + To =:= API], + NewCallgraph = dialyzer_callgraph:add_edges(DirectCalls, Callgraph), + translate_callgraph(Behaviours, Module, NewCallgraph); +translate_callgraph([], _Module, Callgraph) -> + Callgraph. + +%%-------------------------------------------------------------------- + +get_behaviours(Attrs) -> + BehaviourListsAndLine = [{cerl:concrete(L2), hd(cerl:get_ann(L2))} || + {L1, L2} <- Attrs, cerl:is_literal(L1), + cerl:is_literal(L2), cerl:concrete(L1) =:= 'behaviour'], + Behaviours = lists:append([Behs || {Behs,_} <- BehaviourListsAndLine]), + BehLines = [{B,L} || {L1,L} <- BehaviourListsAndLine, B <- L1], + {Behaviours, BehLines}. + +get_warnings(Module, Behaviours, State) -> + get_warnings(Module, Behaviours, State, []). + +get_warnings(_, [], _, Acc) -> + Acc; +get_warnings(Module, [Behaviour|Rest], State, Acc) -> + Warnings = check_behaviour(Module, Behaviour, State), + get_warnings(Module, Rest, State, Warnings ++ Acc). + +check_behaviour(Module, Behaviour, State) -> + try + Callbacks = Behaviour:behaviour_info(callbacks), + Fun = fun({_,_,_}) -> true; + (_) -> false + end, + case lists:any(Fun, Callbacks) of + true -> check_all_callbacks(Module, Behaviour, Callbacks, State); + false -> [] + end + catch + _:_ -> [] + end. + +check_all_callbacks(Module, Behaviour, Callbacks, State) -> + check_all_callbacks(Module, Behaviour, Callbacks, State, []). + +check_all_callbacks(_Module, _Behaviour, [], _State, Acc) -> + Acc; +check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest], State, Acc) -> + Records = dialyzer_codeserver:get_records(State#state.codeserver), + case parse_spec(Spec, Records) of + {ok, Fun, Type} -> + RetType = erl_types:t_fun_range(Type), + ArgTypes = erl_types:t_fun_args(Type), + Warns = check_callback(Module, Behaviour, Fun, Arity, RetType, + ArgTypes, State#state.plt); + Else -> + Warns = [{invalid_spec, [Behaviour, Fun, Arity, reason_spec_error(Else)]}] + end, + check_all_callbacks(Module, Behaviour, Rest, State, Warns ++ Acc); +check_all_callbacks(Module, Behaviour, [{Fun, Arity}|Rest], State, Acc) -> + Warns = {spec_missing, [Behaviour, Fun, Arity]}, + check_all_callbacks(Module, Behaviour, Rest, State, [Warns|Acc]). + +parse_spec(String, Records) -> + case erl_scan:string(String) of + {ok, Tokens, _} -> + case erl_parse:parse(Tokens) of + {ok, Form} -> + case Form of + {attribute, _, 'spec', {{Fun, _}, [TypeForm|_Constraint]}} -> + MaybeRemoteType = erl_types:t_from_form(TypeForm), + try + Type = erl_types:t_solve_remote(MaybeRemoteType, Records), + {ok, Fun, Type} + catch + throw:{error,Msg} -> {spec_remote_error, Msg} + end; + _Other -> not_a_spec + end; + {error, {Line, _, Msg}} -> {spec_parser_error, Line, Msg} + end; + _Other -> + lexer_error + end. + +reason_spec_error({spec_remote_error, Msg}) -> + io_lib:format("Remote type solver error: ~s. Make sure the behaviour source is included in the analysis or the plt",[Msg]); +reason_spec_error(not_a_spec) -> + "This is not a spec"; +reason_spec_error({spec_parser_error, Line, Msg}) -> + io_lib:format("~s line of the spec: ~s", [ordinal(Line),Msg]); +reason_spec_error(lexer_error) -> + "Lexical error". + +ordinal(1) -> "1st"; +ordinal(2) -> "2nd"; +ordinal(3) -> "3rd"; +ordinal(N) when is_integer(N) -> io_lib:format("~wth",[N]). + +check_callback(Module, Behaviour, Fun, Arity, XRetType, XArgTypes, Plt) -> + LookupType = dialyzer_plt:lookup(Plt, {Module, Fun, Arity}), + case LookupType of + {value, {Type,Args}} -> + Warn1 = case unifiable(Type, XRetType) of + [] -> []; + Offenders -> + [{callback_type_mismatch, + [Behaviour, Fun, Arity, erl_types:t_sup(Offenders)]}] + end, + ZipArgs = lists:zip3(lists:seq(1, Arity), Args, XArgTypes), + Warn2 = [{callback_arg_type_mismatch, + [Behaviour, Fun, Arity, N, + erl_types:t_sup(Offenders)]} || + {Offenders, N} <- [check_callback_1(V) || V <- ZipArgs], + Offenders =/= []], + Warn1 ++ Warn2; + _ -> [{callback_missing, [Behaviour, Fun, Arity]}] + end. + +check_callback_1({N, T1, T2}) -> + {unifiable(T1, T2), N}. + +unifiable(Type1, Type2) -> + List1 = erl_types:t_elements(Type1), + List2 = erl_types:t_elements(Type2), + [T || T <- List1, + lists:all(fun(T1) -> + erl_types:t_is_none(erl_types:t_inf(T, T1, opaque)) + end, List2)]. + +add_tag_file_line(_Module, {Tag, [B|_R]} = Warn, State) + when Tag =:= spec_missing; + Tag =:= invalid_spec; + Tag =:= callback_missing -> + {B, Line} = lists:keyfind(B, 1, State#state.behlines), + {?WARN_BEHAVIOUR, {State#state.filename, Line}, Warn}; +add_tag_file_line(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}. + +get_line([Line|_]) when is_integer(Line) -> Line; +get_line([_|Tail]) -> get_line(Tail); +get_line([]) -> -1. + +get_file([{file, File}|_]) -> File; +get_file([_|Tail]) -> get_file(Tail). + +%%------------------------------------------------------------------------------ + +get_behaviours([], _Codeserver, KnownAcc, UnknownAcc) -> + {KnownAcc, UnknownAcc}; +get_behaviours([M|Rest], Codeserver, KnownAcc, UnknownAcc) -> + Tree = dialyzer_codeserver:lookup_mod_code(M, Codeserver), + Attrs = cerl:module_attrs(Tree), + {Behaviours, _BehLines} = get_behaviours(Attrs), + {Known, Unknown} = call_behaviours(Behaviours), + get_behaviours(Rest, Codeserver, Known ++ KnownAcc, Unknown ++ UnknownAcc). + +call_behaviours(Behaviours) -> + call_behaviours(Behaviours, [], []). +call_behaviours([], KnownAcc, UnknownAcc) -> + {lists:reverse(KnownAcc), lists:reverse(UnknownAcc)}; +call_behaviours([Behaviour|Rest], KnownAcc, UnknownAcc) -> + try + Callbacks = Behaviour:behaviour_info(callbacks), + Fun = fun({_,_,_}) -> true; + (_) -> false + end, + case lists:any(Fun, Callbacks) of + false -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc]); + true -> call_behaviours(Rest, [Behaviour | KnownAcc], UnknownAcc) + end + catch + _:_ -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc]) + end. + +%------------------------------------------------------------------------------- + +get_behaviour_apis([], Acc) -> + Acc; +get_behaviour_apis([Behaviour | Rest], Acc) -> + MFAs = [{Behaviour, Fun, Arity} || + {{Fun, Arity}, _} <- behaviour_api_calls(Behaviour)], + get_behaviour_apis(Rest, MFAs ++ Acc). + +%------------------------------------------------------------------------------- + +nth_or_0(0, _List, Zero) -> + Zero; +nth_or_0(N, List, _Zero) -> + lists:nth(N, List). + +%------------------------------------------------------------------------------- + +behaviour_api_calls(gen_server) -> + [{{start_link, 3}, {init, 1, [2]}}, + {{start_link, 4}, {init, 1, [3]}}, + {{start, 3}, {init, 1, [2]}}, + {{start, 4}, {init, 1, [3]}}, + {{call, 2}, {handle_call, 3, [2, 0, 0]}}, + {{call, 3}, {handle_call, 3, [2, 0, 0]}}, + {{multi_call, 2}, {handle_call, 3, [2, 0, 0]}}, + {{multi_call, 3}, {handle_call, 3, [3, 0, 0]}}, + {{multi_call, 4}, {handle_call, 3, [3, 0, 0]}}, + {{cast, 2}, {handle_cast, 2, [2, 0]}}, + {{abcast, 2}, {handle_cast, 2, [2, 0]}}, + {{abcast, 3}, {handle_cast, 2, [3, 0]}}]; +behaviour_api_calls(_Other) -> + []. diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index 21d31df71c..1f79e16449 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -27,7 +27,8 @@ %%%------------------------------------------------------------------- -module(dialyzer_callgraph). --export([all_nodes/1, +-export([add_edges/2, + all_nodes/1, delete/1, finalize/1, is_escaping/2, @@ -55,7 +56,8 @@ -export([cleanup/1, get_digraph/1, get_named_tables/1, get_public_tables/1, get_race_code/1, get_race_detection/1, race_code_new/1, put_race_code/2, put_race_detection/2, put_named_tables/2, - put_public_tables/2]). + put_public_tables/2, put_behaviour_api_calls/2, + get_behaviour_api_calls/1]). -include("dialyzer.hrl"). @@ -97,7 +99,8 @@ race_code = dict:new() :: dict(), public_tables = [] :: [label()], named_tables = [] :: [string()], - race_detection = false :: boolean()}). + race_detection = false :: boolean(), + beh_api_calls = [] :: [{mfa(), mfa()}]}). %% Exported Types @@ -695,3 +698,15 @@ to_ps(#callgraph{} = CG, File, Args) -> Command = io_lib:format("dot -Tps ~s -o ~s ~s", [Args, File, Dot_File]), _ = os:cmd(Command), ok. + +%------------------------------------------------------------------------------- + +-spec put_behaviour_api_calls([{mfa(), mfa()}], callgraph()) -> callgraph(). + +put_behaviour_api_calls(Calls, Callgraph) -> + Callgraph#callgraph{beh_api_calls = Calls}. + +-spec get_behaviour_api_calls(callgraph()) -> [{mfa(), mfa()}]. + +get_behaviour_api_calls(Callgraph) -> + Callgraph#callgraph.beh_api_calls. diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index ab56a4e6d3..724383d06a 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -46,7 +46,8 @@ plt_info = none :: 'none' | dialyzer_plt:plt_info(), report_mode = normal :: rep_mode(), return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(), - stored_warnings = [] :: [dial_warning()] + stored_warnings = [] :: [dial_warning()], + unknown_behaviours = [] :: [atom()] }). %%-------------------------------------------------------------------- @@ -440,7 +441,9 @@ expand_dependent_modules_1([], Included, _ModDeps) -> -spec hipe_compile([file:filename()], #options{}) -> 'ok'. hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) -> - case (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE) orelse ErlangMode of + NoNative = (get(dialyzer_options_native) =:= false), + FewFiles = (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE), + case NoNative orelse FewFiles orelse ErlangMode of true -> ok; false -> case erlang:system_info(hipe_architecture) of @@ -528,6 +531,9 @@ 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} -> @@ -568,6 +574,11 @@ format_log_cache(LogCache) -> store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) -> St#cl_state{stored_warnings = StoredWarnings ++ Warnings}. +-spec store_unknown_behaviours(#cl_state{}, [_]) -> #cl_state{}. + +store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) -> + St#cl_state{unknown_behaviours = Beh ++ Behs}. + -spec error(string()) -> no_return(). error(Msg) -> @@ -602,6 +613,7 @@ return_value(State = #cl_state{erlang_mode = ErlangMode, false -> print_warnings(State), print_ext_calls(State), + print_unknown_behaviours(State), maybe_close_output_file(State), {RetValue, []}; true -> @@ -637,6 +649,40 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) -> do_print_ext_calls(_, [], _) -> ok. +%%print_unknown_behaviours(#cl_state{report_mode = quiet}) -> +%% ok; +print_unknown_behaviours(#cl_state{output = Output, + external_calls = Calls, + 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 =:= [] of + true -> io:nl(Output); %% Need to do a newline first + false -> ok + end, + case Format of + formatted -> + io:put_chars(Output, "Unknown behaviours (behaviour_info(callbacks)" + " does not return any specs):\n"), + do_print_unknown_behaviours(Output, Behaviours, " "); + raw -> + io:put_chars(Output, "%% Unknown behaviours:\n"), + do_print_unknown_behaviours(Output, Behaviours, "%% ") + end + 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, diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index ae466e5c01..1f0da012d6 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -68,6 +68,11 @@ cl(["-n"|T]) -> cl(["--no_check_plt"|T]) -> put(dialyzer_options_check_plt, false), cl(T); +cl(["-nn"|T]) -> + cl(["--no_native"|T]); +cl(["--no_native"|T]) -> + put(dialyzer_options_native, false), + cl(T); cl(["--plt_info"|T]) -> put(dialyzer_options_analysis_type, plt_info), cl(T); @@ -181,7 +186,7 @@ cl([H|_] = L) -> NewTail = command_line(L), cl(NewTail); false -> - error("Unknown option: "++H) + error("Unknown option: " ++ H) end; cl([]) -> {RetTag, Opts} = @@ -191,7 +196,7 @@ cl([]) -> {plt_info, cl_options()}; false -> case get(dialyzer_options_mode) of - {gui,_} = GUI -> {GUI, common_options()}; + {gui, _} = GUI -> {GUI, common_options()}; cl -> case get(dialyzer_options_analysis_type) =:= plt_check of true -> {check_init, cl_options()}; @@ -311,17 +316,27 @@ help_message() -> S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose] [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]* [--output_plt file] [-Wwarn]* [--src] [--gui | --wx] - [-c applications] [-r applications] [-o outfile] + [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] -Options: - -c applications (or --command-line applications) - Use Dialyzer from the command line (no GUI) to detect defects in the - specified applications (directories or .erl or .beam files) - -r applications - Same as -c only that directories are searched recursively for - subdirectories containing .erl or .beam files (depending on the - type of analysis) + [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] + [--no_native] +Options: + files_or_dirs (for backwards compatibility also as: -c files_or_dirs) + Use Dialyzer from the command line to detect defects in the + specified files or directories containing .erl or .beam files, + depending on the type of the analysis + -r dirs + Same as the previous but the specified directories are searched + recursively for subdirectories containing .erl or .beam files in + them, depending on the type of analysis + --apps applications + Option typically used when building or modifying a PLT as in: + dialyzer --build_plt --apps erts kernel stdlib mnesia ... + to conveniently refer to library applications corresponding to the + Erlang/OTP installation. However, the option is general and can also + be used during analysis in order to refer to Erlang/OTP applications. + In addition, file or directory names can also be included, as in: + dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam -o outfile (or --output outfile) When using Dialyzer from the command line, send the analysis results to the specified \"outfile\" rather than to stdout @@ -389,6 +404,10 @@ Options: by the file name extension. Supported extensions are: raw, dot, and ps. If something else is used as file name extension, default format '.raw' will be used. + --no_native (or -nn) + 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. --gui Use the gs-based GUI. --wx @@ -432,6 +451,9 @@ warning_options_msg() -> Include warnings for functions that only return by means of an exception. -Wrace_conditions *** Include warnings for possible race conditions. + -Wbehaviours *** + Include warnings about behaviour callbacks which drift from the published + recommended interfaces. -Wunderspecs *** Warn about underspecified functions (those whose -spec is strictly more allowing than the success typing). diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 624501fc49..3b4b66cb68 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -56,83 +56,83 @@ %%-------------------------------------------------------------------- --record(dialyzer_codeserver, {table_pid :: pid(), - exports = sets:new() :: set(), % set(mfa()) - next_core_label = 0 :: label(), - records = dict:new() :: dict(), - temp_records = dict:new() :: dict(), - contracts = dict:new() :: dict(), - temp_contracts = dict:new() :: dict()}). +-record(codeserver, {table_pid :: pid(), + exports = sets:new() :: set(), % set(mfa()) + next_core_label = 0 :: label(), + records = dict:new() :: dict(), + temp_records = dict:new() :: dict(), + contracts = dict:new() :: dict(), + temp_contracts = dict:new() :: dict()}). --opaque codeserver() :: #dialyzer_codeserver{}. +-opaque codeserver() :: #codeserver{}. %%-------------------------------------------------------------------- -spec new() -> codeserver(). new() -> - #dialyzer_codeserver{table_pid = table__new()}. + #codeserver{table_pid = table__new()}. -spec delete(codeserver()) -> 'ok'. -delete(#dialyzer_codeserver{table_pid = TablePid}) -> +delete(#codeserver{table_pid = TablePid}) -> table__delete(TablePid). -spec insert(module(), cerl:c_module(), codeserver()) -> codeserver(). insert(Mod, ModCode, CS) -> - NewTablePid = table__insert(CS#dialyzer_codeserver.table_pid, Mod, ModCode), - CS#dialyzer_codeserver{table_pid = NewTablePid}. + NewTablePid = table__insert(CS#codeserver.table_pid, Mod, ModCode), + CS#codeserver{table_pid = NewTablePid}. -spec insert_exports([mfa()], codeserver()) -> codeserver(). -insert_exports(List, #dialyzer_codeserver{exports = Exports} = CS) -> +insert_exports(List, #codeserver{exports = Exports} = CS) -> Set = sets:from_list(List), NewExports = sets:union(Exports, Set), - CS#dialyzer_codeserver{exports = NewExports}. + CS#codeserver{exports = NewExports}. -spec is_exported(mfa(), codeserver()) -> boolean(). -is_exported(MFA, #dialyzer_codeserver{exports = Exports}) -> +is_exported(MFA, #codeserver{exports = Exports}) -> sets:is_element(MFA, Exports). -spec get_exports(codeserver()) -> set(). % set(mfa()) -get_exports(#dialyzer_codeserver{exports = Exports}) -> +get_exports(#codeserver{exports = Exports}) -> Exports. -spec lookup_mod_code(module(), codeserver()) -> cerl:c_module(). lookup_mod_code(Mod, CS) when is_atom(Mod) -> - table__lookup(CS#dialyzer_codeserver.table_pid, Mod). + table__lookup(CS#codeserver.table_pid, Mod). -spec lookup_mfa_code(mfa(), codeserver()) -> {cerl:c_var(), cerl:c_fun()}. lookup_mfa_code({_M, _F, _A} = MFA, CS) -> - table__lookup(CS#dialyzer_codeserver.table_pid, MFA). + table__lookup(CS#codeserver.table_pid, MFA). -spec get_next_core_label(codeserver()) -> label(). -get_next_core_label(#dialyzer_codeserver{next_core_label = NCL}) -> +get_next_core_label(#codeserver{next_core_label = NCL}) -> NCL. -spec set_next_core_label(label(), codeserver()) -> codeserver(). set_next_core_label(NCL, CS) -> - CS#dialyzer_codeserver{next_core_label = NCL}. + CS#codeserver{next_core_label = NCL}. -spec store_records(module(), dict(), codeserver()) -> codeserver(). -store_records(Mod, Dict, #dialyzer_codeserver{records = RecDict} = CS) +store_records(Mod, Dict, #codeserver{records = RecDict} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{records = dict:store(Mod, Dict, RecDict)} + false -> CS#codeserver{records = dict:store(Mod, Dict, RecDict)} end. -spec lookup_mod_records(module(), codeserver()) -> dict(). -lookup_mod_records(Mod, #dialyzer_codeserver{records = RecDict}) +lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) -> case dict:find(Mod, RecDict) of error -> dict:new(); @@ -141,45 +141,44 @@ lookup_mod_records(Mod, #dialyzer_codeserver{records = RecDict}) -spec get_records(codeserver()) -> dict(). -get_records(#dialyzer_codeserver{records = RecDict}) -> +get_records(#codeserver{records = RecDict}) -> RecDict. -spec store_temp_records(module(), dict(), codeserver()) -> codeserver(). -store_temp_records(Mod, Dict, #dialyzer_codeserver{temp_records = TempRecDict} = CS) +store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)} + false -> CS#codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)} end. -spec get_temp_records(codeserver()) -> dict(). -get_temp_records(#dialyzer_codeserver{temp_records = TempRecDict}) -> +get_temp_records(#codeserver{temp_records = TempRecDict}) -> TempRecDict. -spec set_temp_records(dict(), codeserver()) -> codeserver(). set_temp_records(Dict, CS) -> - CS#dialyzer_codeserver{temp_records = Dict}. + CS#codeserver{temp_records = Dict}. -spec finalize_records(dict(), codeserver()) -> codeserver(). finalize_records(Dict, CS) -> - CS#dialyzer_codeserver{records = Dict, temp_records = dict:new()}. + CS#codeserver{records = Dict, temp_records = dict:new()}. -spec store_contracts(module(), dict(), codeserver()) -> codeserver(). -store_contracts(Mod, Dict, #dialyzer_codeserver{contracts = C} = CS) - when is_atom(Mod) -> +store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{contracts = dict:store(Mod, Dict, C)} + false -> CS#codeserver{contracts = dict:store(Mod, Dict, C)} end. -spec lookup_mod_contracts(module(), codeserver()) -> dict(). -lookup_mod_contracts(Mod, #dialyzer_codeserver{contracts = ContDict}) +lookup_mod_contracts(Mod, #codeserver{contracts = ContDict}) when is_atom(Mod) -> case dict:find(Mod, ContDict) of error -> dict:new(); @@ -189,7 +188,7 @@ lookup_mod_contracts(Mod, #dialyzer_codeserver{contracts = ContDict}) -spec lookup_mfa_contract(mfa(), codeserver()) -> 'error' | {'ok', dialyzer_contracts:file_contract()}. -lookup_mfa_contract({M,_F,_A} = MFA, #dialyzer_codeserver{contracts = ContDict}) -> +lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) -> case dict:find(M, ContDict) of error -> error; {ok, Dict} -> dict:find(MFA, Dict) @@ -197,27 +196,27 @@ lookup_mfa_contract({M,_F,_A} = MFA, #dialyzer_codeserver{contracts = ContDict}) -spec get_contracts(codeserver()) -> dict(). -get_contracts(#dialyzer_codeserver{contracts = ContDict}) -> +get_contracts(#codeserver{contracts = ContDict}) -> ContDict. -spec store_temp_contracts(module(), dict(), codeserver()) -> codeserver(). -store_temp_contracts(Mod, Dict, #dialyzer_codeserver{temp_contracts = C} = CS) +store_temp_contracts(Mod, Dict, #codeserver{temp_contracts = C} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{temp_contracts = dict:store(Mod, Dict, C)} + false -> CS#codeserver{temp_contracts = dict:store(Mod, Dict, C)} end. -spec get_temp_contracts(codeserver()) -> dict(). -get_temp_contracts(#dialyzer_codeserver{temp_contracts = TempContDict}) -> +get_temp_contracts(#codeserver{temp_contracts = TempContDict}) -> TempContDict. -spec finalize_contracts(dict(), codeserver()) -> codeserver(). finalize_contracts(Dict, CS) -> - CS#dialyzer_codeserver{contracts = Dict, temp_contracts = dict:new()}. + CS#codeserver{contracts = Dict, temp_contracts = dict:new()}. table__new() -> spawn_link(fun() -> table__loop(none, dict:new()) end). diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index e2680bb03d..bad0a8d8cd 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -196,9 +196,13 @@ check_contract(#contract{contracts = Contracts}, SuccType) -> ok -> InfList = [erl_types:t_inf(Contract, SuccType, opaque) || Contract <- Contracts2], - check_contract_inf_list(InfList, SuccType) + case check_contract_inf_list(InfList, SuccType) of + {error, _} = Invalid -> Invalid; + ok -> check_extraneous(Contracts2, SuccType) + end end - catch throw:{error, _} = Error -> Error + catch + throw:{error, _} = Error -> Error end. check_domains([_]) -> ok; @@ -233,6 +237,22 @@ check_contract_inf_list([FunType|Left], SuccType) -> check_contract_inf_list([], _SuccType) -> {error, invalid_contract}. +check_extraneous([], _SuccType) -> ok; +check_extraneous([C|Cs], SuccType) -> + case check_extraneous_1(C, SuccType) of + ok -> check_extraneous(Cs, SuccType); + Error -> Error + end. + +check_extraneous_1(Contract, SuccType) -> + CRngs = erl_types:t_elements(erl_types:t_fun_range(Contract)), + STRng = erl_types:t_fun_range(SuccType), + %% io:format("CR = ~p\nSR = ~p\n", [CRngs, STRng]), + case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of + [] -> ok; + CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}} + end. + %% This is the heart of the "range function" -spec process_contracts([contract_pair()], [erl_types:erl_type()]) -> erl_types:erl_type(). @@ -411,6 +431,8 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], case check_contract(Contract, Sig) of {error, invalid_contract} -> [invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc]; + {error, {extra_range, ExtraRanges, STRange}} -> + [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; {error, Msg} -> [{?WARN_CONTRACT_SYNTAX, FileLine, Msg}|Acc]; ok -> @@ -442,9 +464,15 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], get_invalid_contract_warnings_funs([], _Plt, _RecDict, Acc) -> Acc. -invalid_contract_warning({M, F, A}, FileLine, Type, RecDict) -> +invalid_contract_warning({M, F, A}, FileLine, SuccType, RecDict) -> + SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict), + {?WARN_CONTRACT_TYPES, FileLine, {invalid_contract, [M, F, A, SuccTypeStr]}}. + +extra_range_warning({M, F, A}, FileLine, ExtraRanges, STRange) -> + ERangesStr = erl_types:t_to_string(ExtraRanges), + STRangeStr = erl_types:t_to_string(STRange), {?WARN_CONTRACT_TYPES, FileLine, - {invalid_contract, [M, F, A, dialyzer_utils:format_sig(Type, RecDict)]}}. + {extra_range, [M, F, A, ERangesStr, STRangeStr]}}. picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) -> CSig = erl_types:t_abstract_records(CSig0, RecDict), diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 178321ea18..7fb309497a 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -47,7 +47,7 @@ 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/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, @@ -93,11 +93,13 @@ tree_map :: dict(), warning_mode = false :: boolean(), warnings = [] :: [dial_warning()], - work :: {[_], [_], set()}}). + work :: {[_], [_], set()}, + module :: module(), + behaviour_api_info = [] :: [{atom(),[_]}]}). %% Exported Types --type state() :: #state{}. +-opaque state() :: #state{}. %%-------------------------------------------------------------------- @@ -263,10 +265,15 @@ analyze_module(Tree, Plt, Callgraph) -> analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> debug_pp(Tree, false), Module = cerl:atom_val(cerl:module_name(Tree)), + RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), + BehaviourTranslations = + case RaceDetection of + true -> dialyzer_behaviours:translatable_behaviours(Tree); + false -> [] + end, TopFun = cerl:ann_c_fun([{label, top}], [], Tree), - State = - state__new(dialyzer_callgraph:race_code_new(Callgraph), - TopFun, Plt, Module, Records), + State = state__new(dialyzer_callgraph:race_code_new(Callgraph), + TopFun, Plt, Module, Records, BehaviourTranslations), State1 = state__race_analysis(not GetWarnings, State), State2 = analyze_loop(State1), RaceCode = dialyzer_callgraph:get_race_code(Callgraph), @@ -277,7 +284,18 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> State3 = state__set_warning_mode(State2), State4 = analyze_loop(State3), State5 = state__restore_race_code(RaceCode, State4), - dialyzer_races:race(State5); + + %% EXPERIMENTAL: Turn all behaviour API calls into calls to the + %% respective callback module's functions. + + case BehaviourTranslations of + [] -> dialyzer_races:race(State5); + Behaviours -> + TranslatedCallgraph = + dialyzer_behaviours:translate_callgraph(Behaviours, Module, + State5#state.callgraph), + dialyzer_races:race(State5#state{callgraph = TranslatedCallgraph}) + end; false -> state__restore_race_code( dict:merge(fun (_K, V1, _V2) -> V1 end, @@ -567,6 +585,7 @@ 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) -> @@ -585,9 +604,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], erl_bif_types:type(M, F, A, NewFunArgs) end, {BArgs, BRange}; - false -> GenSig + false -> IsBIF = false, GenSig end; - local -> GenSig + local -> IsBIF = false, GenSig end, {SigArgs, SigRange} = %% if there is hard-coded or contract information with opaque types, @@ -601,18 +620,33 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], none -> {AnyArgs, t_any()} end end, - NewArgsSig = t_inf_lists(SigArgs, ArgTypes), - NewArgsContract = t_inf_lists(CArgs, ArgTypes), - NewArgsBif = t_inf_lists(BifArgs, ArgTypes), - NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract), - NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif), + 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), - ContrRet = CRange(NewArgTypes), - Mode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of + {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 end, - RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, Mode), SigRange, Mode), + RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode), ?debug("--------------------------------------------------------\n", []), ?debug("Fun: ~p\n", [Fun]), ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]), @@ -623,7 +657,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]), ?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(NewArgTypes))]), + ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]), ?debug("SigRet: ~s\n", [erl_types:t_to_string(SigRange)]), State1 = case dialyzer_callgraph:get_race_detection(Callgraph) andalso @@ -632,8 +666,21 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Ann = cerl:get_ann(Tree), File = get_file(Ann), Line = abs(get_line(Ann)), - dialyzer_races:store_race_call(Fun, ArgTypes, Args, {File, Line}, - State); + + %% EXPERIMENTAL: Turn a behaviour's API call into a call to the + %% respective callback module's function. + + Module = State#state.module, + BehApiInfo = State#state.behaviour_api_info, + {RealFun, RealArgTypes, RealArgs} = + case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes, + Args, Module, + BehApiInfo) of + plain_call -> {Fun, ArgTypes, Args}; + BehaviourAPI -> BehaviourAPI + end, + dialyzer_races:store_race_call(RealFun, RealArgTypes, RealArgs, + {File, Line}, State); false -> State end, FailedConj = any_none([RetWithoutLocal|NewArgTypes]), @@ -643,7 +690,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], case FailedConj andalso not (IsFailBif orelse IsFailSig) of true -> FailedSig = any_none(NewArgsSig), - FailedContract = any_none([CRange(NewArgsContract)|NewArgsContract]), + FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]), FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]), InfSig = t_inf(t_fun(SigArgs, SigRange), t_fun(BifArgs, BifRange(BifArgs))), @@ -786,8 +833,11 @@ 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), - case t_is_none(Inf) andalso (not any_none(Ts)) + 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 true -> Args = case erl_types:t_is_opaque(T1) of @@ -905,9 +955,9 @@ 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 *atoms* even if - %% they happen to be identical to an atom which is also involved in - %% the definition of an opaque data type + %% 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)), Map2 = enter_type_lists([M, F], [MType, FType], Map1), @@ -936,13 +986,18 @@ handle_call(Tree, Map, State) -> end, {State2, Map2, t_none()}; false -> - %% XXX: Consider doing this for all combinations of MF - case {t_atom_vals(MType), t_atom_vals(FType)} of - {[MAtom], [FAtom]} -> - FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)}, - State1)}], - handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1); - {_MAtoms, _FAtoms} -> + case t_is_atom(MType) of + true -> + %% XXX: Consider doing this for all combinations of MF + case {t_atom_vals(MType), t_atom_vals(FType)} of + {[MAtom], [FAtom]} -> + FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)}, + State1)}], + handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1); + {_MAtoms, _FAtoms} -> + {State1, Map2, t_any()} + end; + false -> {State1, Map2, t_any()} end end. @@ -1481,10 +1536,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> Cons = t_inf(Type, t_cons()), case t_is_none(Cons) of true -> - case t_find_opaque_mismatch(t_cons(), Type) of - {ok, T1, T2} -> bind_error([Pat], T1, T2, opaque); - error -> bind_error([Pat], Type, t_none(), bind) - end; + bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev); false -> {Map1, [HdType, TlType]} = bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], @@ -1501,18 +1553,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end, case t_is_none(t_inf(LiteralOrOpaque, Type)) of true -> - case t_find_opaque_mismatch(Literal, Type) 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) - end; + bind_opaque_pats(Literal, Type, Pat, Map, State, Rev); false -> {Map, LiteralOrOpaque} end; tuple -> @@ -1534,18 +1575,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> Tuple = t_inf(Prototype, Type), case t_is_none(Tuple) of true -> - case t_find_opaque_mismatch(Prototype, Type) 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) - end; + bind_opaque_pats(Prototype, Type, Pat, Map, State, Rev); false -> SubTuples = t_tuple_subtypes(Tuple), %% Need to call the top function to get the try-catch wrapper @@ -1689,6 +1719,20 @@ bind_bin_segs([], _BinType, Acc, Map, _State) -> bind_error(Pats, Type, OpaqueType, Error) -> throw({error, Error, Pats, Type, OpaqueType}). +bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) -> + case t_find_opaque_mismatch(GenType, Type) 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) + end. + %%---------------------------------------- %% Guards %% @@ -2296,7 +2340,7 @@ bind_guard_list([G|Gs], Map, Env, Eval, State, Acc) -> bind_guard_list([], Map, _Env, _Eval, _State, Acc) -> {Map, lists:reverse(Acc)}. --spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], #state{}) -> +-spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> no_return(). signal_guard_fail(Guard, ArgTypes, State) -> @@ -2327,7 +2371,7 @@ 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(cerl:c_call(), [erl_types:erl_type()], #state{}) -> +-spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> no_return(). signal_guard_fatal_fail(Guard, ArgTypes, State) -> @@ -2680,7 +2724,7 @@ determine_mode(Type, Opaques) -> %%% %%% =========================================================================== -state__new(Callgraph, Tree, Plt, Module, Records) -> +state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt), @@ -2690,7 +2734,8 @@ state__new(Callgraph, Tree, Plt, Module, Records) -> erl_types:t_opaque_from_records(Records), #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, - warning_mode = false, warnings = [], work = Work, tree_map = TreeMap}. + warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, + module = Module, behaviour_api_info = BehaviourTranslations}. state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> Fun = get_label(Fun0), @@ -3197,7 +3242,7 @@ get_file([_|Tail]) -> get_file(Tail). is_compiler_generated(Ann) -> lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1). --spec format_args([term()], [erl_types:erl_type()], #state{}) -> +-spec format_args([term()], [erl_types:erl_type()], state()) -> nonempty_string(). format_args([], [], _State) -> @@ -3205,7 +3250,7 @@ format_args([], [], _State) -> format_args(ArgList, TypeList, State) -> "(" ++ format_args_1(ArgList, TypeList, State) ++ ")". --spec format_args_1([term(),...], [erl_types:erl_type(),...], #state{}) -> +-spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) -> string(). format_args_1([Arg], [Type], State) -> @@ -3235,12 +3280,12 @@ format_arg(Arg) -> Default end. --spec format_type(erl_types:erl_type(), #state{}) -> string(). +-spec format_type(erl_types:erl_type(), state()) -> string(). format_type(Type, #state{records = R}) -> t_to_string(Type, R). --spec format_sig_args(erl_types:erl_type(), #state{}) -> string(). +-spec format_sig_args(erl_types:erl_type(), state()) -> string(). format_sig_args(Type, #state{records = R}) -> SigArgs = t_fun_args(Type), diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl index 670433f003..7d56376730 100644 --- a/lib/dialyzer/src/dialyzer_dep.erl +++ b/lib/dialyzer/src/dialyzer_dep.erl @@ -326,7 +326,7 @@ set__filter(#set{set = Set}, Fun) -> %% -record(output, {type :: 'single' | 'list', - content :: 'none' | #set{} | [{output,_,_}]}). + content :: 'none' | #set{} | [#output{}]}). output(none) -> #output{type = single, content = none}; output(S = #set{}) -> #output{type = single, content = S}; diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index 2d97f88680..5b807804e2 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -102,9 +102,9 @@ create_window(Wx, DialyzerOptions) -> MenuBar = wxMenuBar:new(), wxMenuBar:append(MenuBar, FileMenu, "File"), - wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"), - wxMenuBar:append(MenuBar, PltMenu, "Plt"), - wxMenuBar:append(MenuBar, OptionsMenu, "Options"), + wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"), + wxMenuBar:append(MenuBar, PltMenu, "Plt"), + wxMenuBar:append(MenuBar, OptionsMenu, "Options"), wxMenuBar:append(MenuBar, HelpMenu, "Help"), wxFrame:setMenuBar(Frame, MenuBar), ok = wxFrame:connect(Frame, command_menu_selected), @@ -152,8 +152,8 @@ create_window(Wx, DialyzerOptions) -> AddButton = wxButton:new(Frame, ?Add_Button, [{label, "Add"}]), AddDirButton = wxButton:new(Frame, ?AddDir_Button, [{label, "Add Dir"}]), AddRecButton = wxButton:new(Frame, ?AddRec_Button, [{label, "Add Recursively"}]), - ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]), - ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]), + ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]), + ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]), RunButton = wxButton:new(Frame, ?Run_Button, [{label, "Run"}]), StopButton = wxButton:new(Frame, ?Stop_Button, [{label, "Stop"}]), wxWindow:disable(StopButton), @@ -170,8 +170,8 @@ create_window(Wx, DialyzerOptions) -> wxButton:connect(StopButton, command_button_clicked), %%------------Set Layout ------------ - All = wxBoxSizer:new(?wxVERTICAL), - Top = wxBoxSizer:new(?wxHORIZONTAL), + All = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), Left = wxBoxSizer:new(?wxVERTICAL), Right = wxBoxSizer:new(?wxVERTICAL), RightUp = wxBoxSizer:new(?wxHORIZONTAL), @@ -390,7 +390,7 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, warnings_box = WarningsBox} = State) -> receive #wx{event = #wxClose{}} -> - io:format("~p Closing window ~n", [self()]), + %% io:format("~p Closing window ~n", [self()]), ok = wxFrame:setStatusText(Frame, "Closing...",[]), wxWindow:destroy(Frame), ?RET_NOTHING_SUSPICIOUS; @@ -539,7 +539,7 @@ maybe_quit(#gui_state{frame = Frame} = State) -> %% ------------ Yes/No Question ------------ dialog(#gui_state{frame = Frame}, Message, Title) -> - MessageWin = wxMessageDialog:new(Frame,Message,[{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]), + MessageWin = wxMessageDialog:new(Frame, Message, [{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]), case wxDialog:showModal(MessageWin) of ?wxID_YES -> true; @@ -563,12 +563,12 @@ search_doc_plt(#gui_state{gui = Wx} = State) -> Cancel = wxButton:new(Dialog, ?Search_Cancel, [{label, "Cancel"}]), wxButton:connect(Cancel, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), - Top = wxBoxSizer:new(?wxHORIZONTAL), - ModLayout = wxBoxSizer:new(?wxVERTICAL), - FunLayout = wxBoxSizer:new(?wxVERTICAL), - ArLayout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), + ModLayout = wxBoxSizer:new(?wxVERTICAL), + FunLayout = wxBoxSizer:new(?wxVERTICAL), + ArLayout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(ModLayout, ModLabel, ?BorderOpt), wxSizer:add(ModLayout,ModText, ?BorderOpt), @@ -606,7 +606,7 @@ search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText A = format_search(wxTextCtrl:getValue(ArText)), if - (M == '_') or (F == '_') or (A == '_') -> + (M =:= '_') orelse (F =:= '_') orelse (A =:= '_') -> error_sms(State, "Please give:\n Module (atom)\n Function (atom)\n Arity (integer)\n"), search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel); true -> @@ -670,7 +670,7 @@ free_editor(#gui_state{gui = Wx, frame = Frame}, Title, Contents0) -> wxFrame:connect(Win, close_window), Ok = wxButton:new(Win, ?Message_Ok, [{label, "OK"}]), wxButton:connect(Ok, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), + Layout = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Layout, Editor, ?BorderOpt), wxSizer:add(Layout, Ok, [{flag, ?wxALIGN_CENTER bor ?wxBOTTOM bor ?wxALL}, ?Border]), @@ -757,7 +757,7 @@ add_files(File, FileList, ChosenBox, Ext) -> Files. filter_mods(Mods, Extension) -> - Fun = fun(X) -> + Fun = fun(X) -> filename:extension(X) =:= Extension orelse (filelib:is_dir(X) andalso @@ -944,9 +944,9 @@ include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs], wxListBox:set(Box, Dirs), - Layout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), - Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(Layout, DirLabel, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), wxSizer:add(Layout, DirPicker, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), @@ -1038,12 +1038,12 @@ macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> || {X,Y} <- Options#options.defines], wxListBox:set(Box, Macros), - Layout = wxBoxSizer:new(?wxVERTICAL), - Item = wxBoxSizer:new(?wxHORIZONTAL), - MacroItem = wxBoxSizer:new(?wxVERTICAL), - TermItem = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), - Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Item = wxBoxSizer:new(?wxHORIZONTAL), + MacroItem = wxBoxSizer:new(?wxVERTICAL), + TermItem = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(MacroItem, MacroLabel, ?BorderOpt), wxSizer:add(MacroItem, MacroText, ?BorderOpt), @@ -1159,7 +1159,8 @@ handle_explanation(#gui_state{rawWarnings = RawWarns, warnings_box = WarnBox, expl_pid = ExplPid} = State) -> case wxListBox:isEmpty(WarnBox) of - true -> error_sms(State, "\nThere are no warnings.\nRun the dialyzer first."); + true -> + error_sms(State, "\nThere are no warnings.\nRun the dialyzer first."); false -> case wxListBox:getSelections(WarnBox)of {0, []} -> @@ -1200,8 +1201,8 @@ show_explanation(#gui_state{gui = Wx} = State, Explanation) -> wxButton:connect(ExplButton, command_button_clicked), Ok = wxButton:new(Win, ?ExplOk, [{label, "OK"}]), wxButton:connect(Ok, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(Buttons, ExplButton, ?BorderOpt), wxSizer:add(Buttons, Ok, ?BorderOpt), wxSizer:add(Layout, Editor,[{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 6531073072..1f587d6df0 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -252,6 +252,8 @@ build_warnings([Opt|Opts], Warnings) -> ordsets:add_element(?WARN_RETURN_ONLY_EXIT, Warnings); race_conditions -> ordsets:add_element(?WARN_RACE_CONDITION, Warnings); + behaviours -> + ordsets:add_element(?WARN_BEHAVIOUR, Warnings); specdiffs -> S = ordsets:from_list([?WARN_CONTRACT_SUBTYPE, ?WARN_CONTRACT_SUPERTYPE, diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index f2e0fe1e97..fed1e74b33 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -70,109 +70,106 @@ %%---------------------------------------------------------------------- --record(dialyzer_plt, {info = table_new() :: dict(), - types = table_new() :: dict(), - contracts = table_new() :: dict()}). --opaque plt() :: #dialyzer_plt{}. +-record(plt, {info = table_new() :: dict(), + types = table_new() :: dict(), + contracts = table_new() :: dict()}). +-opaque plt() :: #plt{}. -include("dialyzer.hrl"). -type file_md5() :: {file:filename(), binary()}. -type plt_info() :: {[file_md5()], dict()}. --record(dialyzer_file_plt, {version = "" :: string(), - file_md5_list = [] :: [file_md5()], - info = dict:new() :: dict(), - contracts = dict:new() :: dict(), - types = dict:new() :: dict(), - mod_deps :: mod_deps(), - implementation_md5 = [] :: [file_md5()] - }). +-record(file_plt, {version = "" :: string(), + file_md5_list = [] :: [file_md5()], + info = dict:new() :: dict(), + contracts = dict:new() :: dict(), + types = dict:new() :: dict(), + mod_deps :: mod_deps(), + implementation_md5 = [] :: [file_md5()]}). %%---------------------------------------------------------------------- -spec new() -> plt(). new() -> - #dialyzer_plt{}. + #plt{}. -spec delete_module(plt(), module()) -> plt(). -delete_module(#dialyzer_plt{info = Info, types = Types, contracts = Contracts}, - Mod) -> - #dialyzer_plt{info = table_delete_module(Info, Mod), - types = table_delete_module2(Types, Mod), - contracts = table_delete_module(Contracts, Mod)}. +delete_module(#plt{info = Info, types = Types, contracts = Contracts}, Mod) -> + #plt{info = table_delete_module(Info, Mod), + types = table_delete_module2(Types, Mod), + contracts = table_delete_module(Contracts, Mod)}. -spec delete_list(plt(), [mfa() | integer()]) -> plt(). -delete_list(#dialyzer_plt{info = Info, types = Types, contracts = Contracts}, - List) -> - #dialyzer_plt{info = table_delete_list(Info, List), - types = Types, - contracts = table_delete_list(Contracts, List)}. +delete_list(#plt{info = Info, types = Types, contracts = Contracts}, List) -> + #plt{info = table_delete_list(Info, List), + types = Types, + contracts = table_delete_list(Contracts, List)}. -spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt(). -insert_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) -> - PLT#dialyzer_plt{contracts = table_insert_list(Contracts, List)}. +insert_contract_list(#plt{contracts = Contracts} = PLT, List) -> + PLT#plt{contracts = table_insert_list(Contracts, List)}. -spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}. -lookup_contract(#dialyzer_plt{contracts = Contracts}, +lookup_contract(#plt{contracts = Contracts}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Contracts, MFA). -spec delete_contract_list(plt(), [mfa()]) -> plt(). -delete_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) -> - PLT#dialyzer_plt{contracts = table_delete_list(Contracts, List)}. +delete_contract_list(#plt{contracts = Contracts} = PLT, List) -> + PLT#plt{contracts = table_delete_list(Contracts, List)}. %% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt(). %% -%% insert(#dialyzer_plt{info = Info} = PLT, Id, Types) -> -%% PLT#dialyzer_plt{info = table_insert(Info, Id, Types)}. +%% insert(#plt{info = Info} = PLT, Id, Types) -> +%% PLT#plt{info = table_insert(Info, Id, Types)}. -type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}. -spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt(). -insert_list(#dialyzer_plt{info = Info} = PLT, List) -> - PLT#dialyzer_plt{info = table_insert_list(Info, List)}. +insert_list(#plt{info = Info} = PLT, List) -> + PLT#plt{info = table_insert_list(Info, List)}. -spec lookup(plt(), integer() | mfa_patt()) -> 'none' | {'value', ret_args_types()}. -lookup(#dialyzer_plt{info = Info}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> +lookup(#plt{info = Info}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Info, MFA); -lookup(#dialyzer_plt{info = Info}, Label) when is_integer(Label) -> +lookup(#plt{info = Info}, Label) when is_integer(Label) -> table_lookup(Info, Label). -spec insert_types(plt(), dict()) -> plt(). insert_types(PLT, Rec) -> - PLT#dialyzer_plt{types = Rec}. + PLT#plt{types = Rec}. -spec get_types(plt()) -> dict(). -get_types(#dialyzer_plt{types = Types}) -> +get_types(#plt{types = Types}) -> Types. -type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}. -spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}. -lookup_module(#dialyzer_plt{info = Info}, M) when is_atom(M) -> +lookup_module(#plt{info = Info}, M) when is_atom(M) -> table_lookup_module(Info, M). -spec contains_module(plt(), module()) -> boolean(). -contains_module(#dialyzer_plt{info = Info, contracts = Cs}, M) when is_atom(M) -> +contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> table_contains_module(Info, M) orelse table_contains_module(Cs, M). -spec contains_mfa(plt(), mfa()) -> boolean(). -contains_mfa(#dialyzer_plt{info = Info, contracts = Contracts}, MFA) -> +contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) -> (table_lookup(Info, MFA) =/= none) orelse (table_lookup(Contracts, MFA) =/= none). @@ -208,14 +205,14 @@ from_file(FileName, ReturnInfo) -> Msg = io_lib:format("Old PLT file ~s\n", [FileName]), error(Msg); ok -> - Plt = #dialyzer_plt{info = Rec#dialyzer_file_plt.info, - types = Rec#dialyzer_file_plt.types, - contracts = Rec#dialyzer_file_plt.contracts}, + Plt = #plt{info = Rec#file_plt.info, + types = Rec#file_plt.types, + contracts = Rec#file_plt.contracts}, case ReturnInfo of false -> Plt; true -> - PltInfo = {Rec#dialyzer_file_plt.file_md5_list, - Rec#dialyzer_file_plt.mod_deps}, + PltInfo = {Rec#file_plt.file_md5_list, + Rec#file_plt.mod_deps}, {Plt, PltInfo} end end; @@ -230,25 +227,25 @@ from_file(FileName, ReturnInfo) -> included_files(FileName) -> case get_record_from_file(FileName) of - {ok, #dialyzer_file_plt{file_md5_list = Md5}} -> + {ok, #file_plt{file_md5_list = Md5}} -> {ok, [File || {File, _} <- Md5]}; {error, _What} = Error -> Error end. -check_version(#dialyzer_file_plt{version=?VSN, implementation_md5=ImplMd5}) -> +check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) -> case compute_new_md5(ImplMd5, [], []) of ok -> ok; {differ, _, _} -> error; {error, _} -> error end; -check_version(#dialyzer_file_plt{}) -> error. +check_version(#file_plt{}) -> error. get_record_from_file(FileName) -> case file:read_file(FileName) of {ok, Bin} -> try binary_to_term(Bin) of - #dialyzer_file_plt{} = FilePLT -> {ok, FilePLT}; + #file_plt{} = FilePLT -> {ok, FilePLT}; _ -> {error, not_valid} catch _:_ -> {error, not_valid} @@ -262,30 +259,30 @@ get_record_from_file(FileName) -> -spec merge_plts([plt()]) -> plt(). merge_plts(List) -> - InfoList = [Info || #dialyzer_plt{info = Info} <- List], - TypesList = [Types || #dialyzer_plt{types = Types} <- List], - ContractsList = [Contracts || #dialyzer_plt{contracts = Contracts} <- List], - #dialyzer_plt{info = table_merge(InfoList), - types = table_merge(TypesList), - contracts = table_merge(ContractsList)}. + InfoList = [Info || #plt{info = Info} <- List], + TypesList = [Types || #plt{types = Types} <- List], + ContractsList = [Contracts || #plt{contracts = Contracts} <- List], + #plt{info = table_merge(InfoList), + types = table_merge(TypesList), + contracts = table_merge(ContractsList)}. -spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'. to_file(FileName, - #dialyzer_plt{info = Info, types = Types, contracts = Contracts}, + #plt{info = Info, types = Types, contracts = Contracts}, ModDeps, {MD5, OldModDeps}) -> NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) end, OldModDeps, ModDeps), ImplMd5 = compute_implementation_md5(), - Record = #dialyzer_file_plt{version = ?VSN, - file_md5_list = MD5, - info = Info, - contracts = Contracts, - types = Types, - mod_deps = NewModDeps, - implementation_md5 = ImplMd5}, + Record = #file_plt{version = ?VSN, + file_md5_list = MD5, + info = Info, + contracts = Contracts, + types = Types, + mod_deps = NewModDeps, + implementation_md5 = ImplMd5}, Bin = term_to_binary(Record, [compressed]), case file:write_file(FileName, Bin) of ok -> ok; @@ -307,7 +304,7 @@ to_file(FileName, check_plt(FileName, RemoveFiles, AddFiles) -> case get_record_from_file(FileName) of - {ok, #dialyzer_file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> + {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> case check_version(Rec) of ok -> case compute_new_md5(Md5, RemoveFiles, AddFiles) of @@ -420,18 +417,17 @@ init_md5_list_1(Md5List, [], Acc) -> -spec get_specs(plt()) -> string(). -get_specs(#dialyzer_plt{info = Info}) -> +get_specs(#plt{info = Info}) -> %% TODO: Should print contracts as well. - List = - lists:sort([{MFA, Val} || {MFA = {_,_,_}, Val} <- table_to_list(Info)]), - lists:flatten(create_specs(List, [])). + L = lists:sort([{MFA, Val} || {{_,_,_} = MFA, Val} <- table_to_list(Info)]), + lists:flatten(create_specs(L, [])). beam_file_to_module(Filename) -> list_to_atom(filename:basename(Filename, ".beam")). -spec get_specs(plt(), module(), atom(), arity_patt()) -> 'none' | string(). -get_specs(#dialyzer_plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> +get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> MFA = {M, F, A}, case table_lookup(Info, MFA) of none -> none; @@ -526,9 +522,9 @@ table_merge([H|T]) -> table_merge([], Acc) -> Acc; -table_merge([Plt|Left], Acc) -> +table_merge([Plt|Plts], Acc) -> NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc), - table_merge(Left, NewAcc). + table_merge(Plts, NewAcc). %%--------------------------------------------------------------------------- %% Debug utilities. @@ -538,7 +534,7 @@ table_merge([Plt|Left], Acc) -> pp_non_returning() -> PltFile = get_default_plt(), Plt = from_file(PltFile), - List = table_to_list(Plt#dialyzer_plt.info), + List = table_to_list(Plt#plt.info), Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, erl_types:t_is_unit(Ret)], None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index 5857f7a03d..303e64e68e 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -50,8 +50,10 @@ -define(local, 5). -define(no_arg, no_arg). -define(no_label, no_label). +-define(bypassed, bypassed). -define(WARN_WHEREIS_REGISTER, warn_whereis_register). +-define(WARN_WHEREIS_UNREGISTER, warn_whereis_unregister). -define(WARN_ETS_LOOKUP_INSERT, warn_ets_lookup_insert). -define(WARN_MNESIA_DIRTY_READ_WRITE, warn_mnesia_dirty_read_write). -define(WARN_NO_WARN, warn_no_warn). @@ -64,27 +66,29 @@ -type mfa_or_funlbl() :: label() | mfa(). --type label_type() :: label() | [label()] | {label()} | ?no_label. --type args() :: [label_type() | [string()]]. --type core_vars() :: cerl:cerl() | ?no_arg. --type var_to_map() :: core_vars() | [cerl:cerl()]. --type core_args() :: [core_vars()] | 'empty'. --type op() :: 'bind' | 'unbind'. +-type label_type() :: label() | [label()] | {label()} | ?no_label. +-type args() :: [label_type() | [string()]]. +-type core_vars() :: cerl:cerl() | ?no_arg | ?bypassed. +-type var_to_map1() :: core_vars() | [cerl:cerl()]. +-type var_to_map2() :: cerl:cerl() | [cerl:cerl()] | ?bypassed. +-type core_args() :: [core_vars()] | 'empty'. +-type op() :: 'bind' | 'unbind'. -type dep_calls() :: 'whereis' | 'ets_lookup' | 'mnesia_dirty_read'. --type warn_calls() :: 'register' | 'ets_insert' | 'mnesia_dirty_write'. --type call() :: 'whereis' | 'register' | 'ets_new' | 'ets_lookup' - | 'ets_insert' | 'mnesia_dirty_read1' +-type warn_calls() :: 'register' | 'unregister' | 'ets_insert' + | 'mnesia_dirty_write'. +-type call() :: 'whereis' | 'register' | 'unregister' | 'ets_new' + | 'ets_lookup' | 'ets_insert' | 'mnesia_dirty_read1' | 'mnesia_dirty_read2' | 'mnesia_dirty_write1' | 'mnesia_dirty_write2' | 'function_call'. --type race_tag() :: 'whereis_register' | 'ets_lookup_insert' - | 'mnesia_dirty_read_write'. +-type race_tag() :: 'whereis_register' | 'whereis_unregister' + | 'ets_lookup_insert' | 'mnesia_dirty_read_write'. --record(beg_clause, {arg :: var_to_map(), - pats :: var_to_map(), +-record(beg_clause, {arg :: var_to_map1(), + pats :: var_to_map1(), guard :: cerl:cerl()}). --record(end_clause, {arg :: var_to_map(), - pats :: var_to_map(), +-record(end_clause, {arg :: var_to_map1(), + pats :: var_to_map1(), guard :: cerl:cerl()}). -record(end_case, {clauses :: [#end_clause{}]}). -record(curr_fun, {status :: 'in' | 'out', @@ -98,15 +102,15 @@ args :: args(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()], - state :: _, + state :: _, %% XXX: recursive file_line :: file_line(), var_map :: dict()}). -record(fun_call, {caller :: mfa_or_funlbl(), callee :: mfa_or_funlbl(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()]}). --record(let_tag, {var :: var_to_map(), - arg :: var_to_map()}). +-record(let_tag, {var :: var_to_map1(), + arg :: var_to_map1()}). -record(warn_call, {call_name :: warn_calls(), args :: args(), var_map :: dict()}). @@ -180,6 +184,14 @@ store_race_call(Fun, ArgTypes, Args, FileLine, State) -> fun_mfa = CurrFun, fun_label = CurrFunLabel}, {[#warn_call{call_name = register, args = VarArgs}| RaceList], RaceListSize + 1, [RaceFun|RaceTags], no_t}; + {erlang, unregister, 1} -> + VarArgs = format_args(Args, ArgTypes, CleanState, unregister), + RaceFun = #race_fun{mfa = Fun, args = VarArgs, + arg_types = ArgTypes, vars = Args, + file_line = FileLine, index = RaceListSize, + fun_mfa = CurrFun, fun_label = CurrFunLabel}, + {[#warn_call{call_name = unregister, args = VarArgs}| + RaceList], RaceListSize + 1, [RaceFun|RaceTags], no_t}; {erlang, whereis, 1} -> VarArgs = format_args(Args, ArgTypes, CleanState, whereis), {[#dep_call{call_name = whereis, args = VarArgs, @@ -280,6 +292,7 @@ race(State) -> RaceWarnTag = case Fun of {erlang, register, 2} -> ?WARN_WHEREIS_REGISTER; + {erlang, unregister, 1} -> ?WARN_WHEREIS_UNREGISTER; {ets, insert, 2} -> ?WARN_ETS_LOOKUP_INSERT; {mnesia, dirty_write, _A} -> ?WARN_MNESIA_DIRTY_READ_WRITE end, @@ -287,7 +300,7 @@ race(State) -> state__renew_curr_fun(CurrFun, state__renew_curr_fun_label(CurrFunLabel, state__renew_race_list(lists:nthtail(length(RaceList) - Index, - RaceList), State))), + RaceList), State))), DepList = fixup_race_list(RaceWarnTag, VarArgs, State1), {State2, RaceWarn} = get_race_warn(Fun, Args, ArgTypes, DepList, State), @@ -309,6 +322,7 @@ fixup_race_list(RaceWarnTag, WarnVarArgs, State) -> RaceTag = case RaceWarnTag of ?WARN_WHEREIS_REGISTER -> whereis_register; + ?WARN_WHEREIS_UNREGISTER -> whereis_unregister; ?WARN_ETS_LOOKUP_INSERT -> ets_lookup_insert; ?WARN_MNESIA_DIRTY_READ_WRITE -> mnesia_dirty_read_write end, @@ -320,11 +334,9 @@ fixup_race_list(RaceWarnTag, WarnVarArgs, State) -> lists:reverse(NewRaceList), [], CurrFun, WarnVarArgs, RaceWarnTag, dict:new(), [], [], [], 2 * ?local, NewState), - Parents = - fixup_race_backward(CurrFun, Calls, Calls, [], ?local), + Parents = fixup_race_backward(CurrFun, Calls, Calls, [], ?local), UParents = lists:usort(Parents), - Filtered = - filter_parents(UParents, UParents, Digraph), + Filtered = filter_parents(UParents, UParents, Digraph), NewParents = case lists:member(CurrFun, Filtered) of true -> Filtered; @@ -401,8 +413,7 @@ fixup_race_forward_pullout(CurrFun, CurrFunLabel, Calls, Code, RaceList, false -> {ok, Fun} = Name, {ok, Int} = Label, - case dict:find(Fun, - dialyzer_callgraph:get_race_code(Callgraph)) of + case dict:find(Fun, dialyzer_callgraph:get_race_code(Callgraph)) of error -> {NewCurrFun, NewCurrFunLabel, NewCalls, Tail, NewRaceList, NewRaceVarMap, NewFunDefVars, NewFunCallVars, NewFunArgTypes, @@ -459,7 +470,8 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, case Head of #dep_call{call_name = whereis} -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> {[Head#dep_call{var_map = RaceVarMap}|RaceList], [], NestingLevel, false}; _Other -> @@ -493,9 +505,11 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, _Other -> {RaceList, [], NestingLevel, false} end; - #warn_call{call_name = register} -> + #warn_call{call_name = RegCall} when RegCall =:= register orelse + RegCall =:= unregister -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> {[Head#warn_call{var_map = RaceVarMap}|RaceList], [], NestingLevel, false}; _Other -> @@ -575,6 +589,10 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, {[#warn_call{call_name = register, args = WarnVarArgs, var_map = RaceVarMap}], NewDepList}; + whereis_unregister -> + {[#warn_call{call_name = unregister, args = WarnVarArgs, + var_map = RaceVarMap}], + NewDepList}; ets_lookup_insert -> NewWarnCall = [#warn_call{call_name = ets_insert, args = WarnVarArgs, @@ -760,6 +778,19 @@ get_deplist_paths(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel, _ -> {[Vars, WVA2, WVA3, WVA4], false} end; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs1, + Vars = + lists:flatten( + [find_all_bound_vars(V, RaceVarMap1) || V <- WVA1]), + case {Vars, CurrLevel} of + {[], 0} -> + {WarnVarArgs, true}; + {[], _} -> + {WarnVarArgs, false}; + _ -> + {[Vars, WVA2], false} + end; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs1, Vars1 = @@ -805,8 +836,9 @@ get_deplist_paths(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel, get_deplist_paths(Tail, WarnVarArgs2, RaceWarnTag, RaceVarMap1, CurrLevel1, PublicTables, NamedTables) end; - #warn_call{call_name = register, args = WarnVarArgs1, - var_map = RaceVarMap1} -> + #warn_call{call_name = RegCall, args = WarnVarArgs1, + var_map = RaceVarMap1} when RegCall =:= register orelse + RegCall =:= unregister -> case compare_first_arg(WarnVarArgs, WarnVarArgs1, RaceVarMap1) of true -> {[], false, false}; NewWarnVarArgs -> @@ -1416,7 +1448,8 @@ lists_get(N, List) -> lists:nth(N, List). refine_race(RaceCall, WarnVarArgs, RaceWarnTag, DependencyList, RaceVarMap) -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> case RaceCall of #dep_call{call_name = ets_lookup} -> DependencyList; @@ -1658,6 +1691,20 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> compare_var_list(VA1, WVA1, RaceVarMap) orelse compare_argtypes(VA2, WVA2) + end + end; + ?WARN_WHEREIS_UNREGISTER -> + [VA1, VA2] = VarArgs, + [WVA1, WVA2] = WarnVarArgs, + case any_args(VA2) of + true -> compare_var_list(VA1, WVA1, RaceVarMap); + false -> + case any_args(WVA2) of + true -> compare_var_list(VA1, WVA1, RaceVarMap); + false -> + compare_var_list(VA1, WVA1, RaceVarMap) orelse + compare_argtypes(VA2, WVA2) + end end; ?WARN_ETS_LOOKUP_INSERT -> @@ -1683,8 +1730,8 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> true -> compare_var_list(VA3, WVA3, RaceVarMap); false -> - compare_var_list(VA3, WVA3, RaceVarMap) - orelse compare_argtypes(VA4, WVA4) + compare_var_list(VA3, WVA3, RaceVarMap) orelse + compare_argtypes(VA4, WVA4) end end); ?WARN_MNESIA_DIRTY_READ_WRITE -> @@ -1818,6 +1865,7 @@ ets_tuple_argtypes1(Str, Tuple, TupleList, NestingLevel) -> end end. +format_arg(?bypassed) -> ?no_label; format_arg(Arg) -> case cerl:type(Arg) of var -> cerl_trees:get_label(Arg); @@ -1845,9 +1893,13 @@ format_args_1([Arg], [Type], CleanState) -> [format_arg(Arg), format_type(Type, CleanState)]; format_args_1([Arg|Args], [Type|Types], CleanState) -> List = - case cerl:is_literal(Arg) of - true -> [?no_label, format_cerl(Arg)]; - false -> [format_arg(Arg), format_type(Type, CleanState)] + case Arg =:= ?bypassed of + true -> [?no_label, format_type(Type, CleanState)]; + false -> + case cerl:is_literal(Arg) of + true -> [?no_label, format_cerl(Arg)]; + false -> [format_arg(Arg), format_type(Type, CleanState)] + end end, List ++ format_args_1(Args, Types, CleanState). @@ -1859,6 +1911,9 @@ format_args_2(StrArgList, Call) -> register -> lists_key_replace(2, StrArgList, string:tokens(lists:nth(2, StrArgList), " |")); + unregister -> + lists_key_replace(2, StrArgList, + string:tokens(lists:nth(2, StrArgList), " |")); ets_new -> StrArgList1 = lists_key_replace(2, StrArgList, string:tokens(lists:nth(2, StrArgList), " |")), @@ -1919,10 +1974,11 @@ mnesia_tuple_argtypes(TupleStr) -> [TupleStr2|_T] = string:tokens(TupleStr1, " ,"), lists:flatten(string:tokens(TupleStr2, " |")). --spec race_var_map(var_to_map(), cerl:cerl() | [cerl:cerl()], dict(), op()) -> dict(). +-spec race_var_map(var_to_map1(), var_to_map2(), dict(), op()) -> dict(). race_var_map(Vars1, Vars2, RaceVarMap, Op) -> - case Vars1 =:= ?no_arg of + case Vars1 =:= ?no_arg orelse Vars1 =:= ?bypassed + orelse Vars2 =:= ?bypassed of true -> RaceVarMap; false -> case is_list(Vars1) andalso is_list(Vars2) of @@ -2076,7 +2132,7 @@ race_var_map_guard(Arg, Pats, Guard, RaceVarMap, Op) -> {RaceVarMap1, RemoveClause orelse RemoveClause1}. race_var_map_guard_helper1(Arg, Pats, RaceVarMap, Op) -> - case Arg =:= ?no_arg of + case Arg =:= ?no_arg orelse Arg =:= ?bypassed of true -> {RaceVarMap, false}; false -> case cerl:type(Arg) of @@ -2139,7 +2195,7 @@ unbind_dict_vars(Var1, Var2, RaceVarMap) -> true -> unbind_dict_vars(Var1, Var2, bind_dict_vars_list(Var1, Labels -- [Var2], - dict:erase(Var1, RaceVarMap))); + dict:erase(Var1, RaceVarMap))); false -> unbind_dict_vars_helper(Labels, Var1, Var2, RaceVarMap) end @@ -2171,6 +2227,10 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, ArgNos = lists_key_members_lists(WVA1, FunDefArgs), [[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2, WVA3, WVA4]; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs, + ArgNos = lists_key_members_lists(WVA1, FunDefArgs), + [[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2]; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, ArgNos1 = lists_key_members_lists(WVA1, FunDefArgs), @@ -2185,8 +2245,7 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) -> var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, RaceVarMap, CleanState) -> - FunVarArgs = format_args(FunDefArgs, FunCallTypes, CleanState, - function_call), + FunVarArgs = format_args(FunDefArgs, FunCallTypes, CleanState, function_call), case RaceWarnTag of ?WARN_WHEREIS_REGISTER -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, @@ -2197,6 +2256,15 @@ var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, NewWVA2 = string:tokens(lists:nth(N + 1, FunVarArgs), " |"), [Vars, NewWVA2, WVA3, WVA4] end; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs, + Vars = find_all_bound_vars(WVA1, RaceVarMap), + case lists_key_member_lists(Vars, FunVarArgs) of + 0 -> [Vars, WVA2]; + N when is_integer(N) -> + NewWVA2 = string:tokens(lists:nth(N + 1, FunVarArgs), " |"), + [Vars, NewWVA2] + end; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, Vars1 = find_all_bound_vars(WVA1, RaceVarMap), @@ -2278,6 +2346,10 @@ get_race_warnings_helper(Warnings, State) -> get_reason(lists:keysort(7, DepList), "might fail due to a possible race condition " "caused by its combination with "); + ?WARN_WHEREIS_UNREGISTER -> + get_reason(lists:keysort(7, DepList), + "might fail due to a possible race condition " + "caused by its combination with "); ?WARN_ETS_LOOKUP_INSERT -> get_reason(lists:keysort(7, DepList), "might have an unintended effect due to " ++ @@ -2335,7 +2407,7 @@ state__add_race_warning(State, RaceWarn, RaceWarnTag, FileLine) -> %%% %%% =========================================================================== --spec beg_clause_new(var_to_map(), var_to_map(), cerl:cerl()) -> +-spec beg_clause_new(var_to_map1(), var_to_map1(), cerl:cerl()) -> #beg_clause{}. beg_clause_new(Arg, Pats, Guard) -> @@ -2351,7 +2423,7 @@ cleanup(#races{race_list = RaceList}) -> end_case_new(Clauses) -> #end_case{clauses = Clauses}. --spec end_clause_new(var_to_map(), var_to_map(), cerl:cerl()) -> +-spec end_clause_new(var_to_map1(), var_to_map1(), cerl:cerl()) -> #end_clause{}. end_clause_new(Arg, Pats, Guard) -> @@ -2387,7 +2459,7 @@ get_race_list(#races{race_list = RaceList}) -> get_race_list_size(#races{race_list_size = RaceListSize}) -> RaceListSize. --spec let_tag_new(var_to_map(), var_to_map()) -> #let_tag{}. +-spec let_tag_new(var_to_map1(), var_to_map1()) -> #let_tag{}. let_tag_new(Var, Arg) -> #let_tag{var = Var, arg = Arg}. @@ -2422,5 +2494,4 @@ put_race_analysis(Analysis, Races) -> races(). put_race_list(RaceList, RaceListSize, Races) -> - Races#races{race_list = RaceList, - race_list_size = RaceListSize}. + Races#races{race_list = RaceList, race_list_size = RaceListSize}. diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index dd8480f1f2..1670a6cf89 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -29,7 +29,7 @@ -export([analyze_callgraph/3, analyze_callgraph/4, - get_warnings/6]). + get_warnings/7]). %% These are only intended as debug functions. -export([doit/1, @@ -106,19 +106,21 @@ get_refined_success_typings(State) -> -type doc_plt() :: 'undefined' | dialyzer_plt:plt(). -spec get_warnings(dialyzer_callgraph:callgraph(), dialyzer_plt:plt(), doc_plt(), dialyzer_codeserver:codeserver(), set(), - pid()) -> + pid(), boolean()) -> {[dial_warning()], dialyzer_plt:plt(), doc_plt()}. -get_warnings(Callgraph, Plt, DocPlt, Codeserver, NoWarnUnused, Parent) -> +get_warnings(Callgraph, Plt, DocPlt, Codeserver, + NoWarnUnused, Parent, BehavioursChk) -> InitState = #st{callgraph = Callgraph, codeserver = Codeserver, no_warn_unused = NoWarnUnused, parent = Parent, plt = Plt}, NewState = get_refined_success_typings(InitState), Mods = dialyzer_callgraph:modules(NewState#st.callgraph), CWarns = dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, NewState#st.plt), - get_warnings_from_modules(Mods, NewState, DocPlt, CWarns). + get_warnings_from_modules(Mods, NewState, DocPlt, BehavioursChk, CWarns). -get_warnings_from_modules([M|Ms], State, DocPlt, Acc) when is_atom(M) -> +get_warnings_from_modules([M|Ms], State, DocPlt, + BehavioursChk, Acc) when is_atom(M) -> send_log(State#st.parent, io_lib:format("Getting warnings for ~w\n", [M])), #st{callgraph = Callgraph, codeserver = Codeserver, no_warn_unused = NoWarnUnused, plt = Plt} = State, @@ -131,13 +133,20 @@ get_warnings_from_modules([M|Ms], State, DocPlt, Acc) when is_atom(M) -> dialyzer_contracts:contracts_without_fun(Contracts, AllFuns, Callgraph), {Warnings2, FunTypes, RaceCode, PublicTables, NamedTables} = dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Records, NoWarnUnused), + Attrs = cerl:module_attrs(ModCode), + Warnings3 = if BehavioursChk -> + dialyzer_behaviours:check_callbacks(M, Attrs, + Plt, Codeserver); + true -> [] + end, NewDocPlt = insert_into_doc_plt(FunTypes, Callgraph, DocPlt), NewCallgraph = dialyzer_callgraph:renew_race_info(Callgraph, RaceCode, PublicTables, NamedTables), State1 = st__renew_state_calls(NewCallgraph, State), - get_warnings_from_modules(Ms, State1, NewDocPlt, [Warnings1,Warnings2|Acc]); -get_warnings_from_modules([], #st{plt = Plt}, DocPlt, Acc) -> + get_warnings_from_modules(Ms, State1, NewDocPlt, BehavioursChk, + [Warnings1, Warnings2, Warnings3|Acc]); +get_warnings_from_modules([], #st{plt = Plt}, DocPlt, _, Acc) -> {lists:flatten(Acc), Plt, DocPlt}. refine_succ_typings(ModulePostorder, State) -> diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index aeb20d4fae..57d869b88a 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -43,6 +43,7 @@ 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_subtype/2, 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, @@ -51,7 +52,7 @@ 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_timeout/0, t_tuple/0, t_tuple/1, - t_unify/2, t_var/1, t_var_name/1, + t_unify/3, t_var/1, t_var_name/1, t_none/0, t_unit/0]). -include("dialyzer.hrl"). @@ -71,14 +72,20 @@ rhs :: fvar_or_type(), deps :: [dep()]}). +-type constraint() :: #constraint{}. + -record(constraint_list, {type :: 'conj' | 'disj', - list :: [_], % [constr()] but it needs recursion :-( + list :: [constr()], deps :: [dep()], id :: {'list', dep()}}). +-type constraint_list() :: #constraint_list{}. + -record(constraint_ref, {id :: type_var(), deps :: [dep()]}). --type constr() :: #constraint{} | #constraint_list{} | #constraint_ref{}. +-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 @@ -90,6 +97,7 @@ fun_arities = dict:new() :: dict(), in_match = false :: boolean(), in_guard = false :: boolean(), + module :: module(), name_map = dict:new() :: dict(), next_label :: label(), non_self_recs = [] :: [label()], @@ -98,7 +106,7 @@ records = dict:new() :: dict(), opaques = [] :: [erl_types:erl_type()], scc = [] :: [type_var()]}). - + %%----------------------------------------------------------------------------- -define(TYPE_LIMIT, 4). @@ -631,6 +639,9 @@ 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, + {FunModule, _, _} = MFA, case dialyzer_plt:lookup_contract(Plt, MFA) of none -> case PltRes of @@ -651,7 +662,14 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> %% Need to combine the contract with the success typing. {mk_fun_var( fun(Map) -> - ArgTypes = lookup_type_list(ArgVars, 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) end, ArgVars), @@ -681,8 +699,8 @@ filter_match_fail([]) -> %% If there is a significant number of clauses, we cannot apply the %% list subtraction scheme since it causes the analysis to be too %% slow. Typically, this only affects automatically generated files. -%% Anyway, and the dataflow analysis doesn't suffer from this, so we -%% will get some information anyway. +%% The dataflow analysis doesn't suffer from this, so we will get some +%% information anyway. -define(MAX_NOF_CLAUSES, 15). handle_clauses(Clauses, TopVar, Arg, DefinedVars, State) -> @@ -843,7 +861,7 @@ get_underapprox_from_guard(Tree, Map) -> (cerl:is_c_var(Arg2) orelse cerl:is_literal(Arg2))) of true -> {Arg1Type, _} = get_underapprox_from_guard(Arg1, Map), - {Arg2Type, _} = get_underapprox_from_guard(Arg1, Map), + {Arg2Type, _} = get_underapprox_from_guard(Arg2, Map), case (t_is_equal(True, Arg1Type) andalso t_is_equal(True, Arg2Type)) of true -> {True, Map}; @@ -1511,13 +1529,21 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) -> Cs = mk_constraints(Args, sub, ArgTypes), mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|Cs]) 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) -> - TmpArgTypes = lookup_type_list(Args, Map), + TmpArgTypes0 = lookup_type_list(Args, Map), + TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0], erl_bif_types:type(M, F, A, TmpArgTypes) end, Args), case erl_bif_types:is_known(M, F, A) of @@ -1815,7 +1841,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) of + case solve_one_c(C, Map, State#state.opaques) of error -> ?debug("+++++++++++\nFailed: ~s :: ~s ~w ~s :: ~s\n+++++++++++\n", [format_type(C#constraint.lhs), @@ -1830,7 +1856,7 @@ 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) -> +solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) -> LhsType = lookup_type(Lhs, Map), RhsType = lookup_type(Rhs, Map), Inf = t_inf(LhsType, RhsType, opaque), @@ -1841,16 +1867,16 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) -> true -> error; false -> case Op of - sub -> solve_subtype(Lhs, Inf, Map); + sub -> solve_subtype(Lhs, Inf, Map, Opaques); eq -> - case solve_subtype(Lhs, Inf, Map) of + case solve_subtype(Lhs, Inf, Map, Opaques) of error -> error; - {ok, Map1} -> solve_subtype(Rhs, Inf, Map1) + {ok, Map1} -> solve_subtype(Rhs, Inf, Map1, Opaques) end end end. -solve_subtype(Type, Inf, Map) -> +solve_subtype(Type, Inf, Map, Opaques) -> %% case cerl:is_literal(Type) of %% true -> %% case t_is_subtype(t_from_term(cerl:concrete(Type)), Inf) of @@ -1858,7 +1884,7 @@ solve_subtype(Type, Inf, Map) -> %% false -> error %% end; %% false -> - try t_unify(Type, Inf) of + try t_unify(Type, Inf, Opaques) of {_, List} -> {ok, enter_type_list(List, Map)} catch throw:{mismatch, _T1, _T2} -> @@ -2046,7 +2072,7 @@ state__set_rec_dict(State, 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}. + State#state{opaques = Opaques, module = M}. state__lookup_record(#state{records = Records}, Tag, Arity) -> case erl_types:lookup_record(Tag, Arity, Records) of @@ -2258,7 +2284,7 @@ mk_constraint(Lhs, Op, Rhs) -> case Deps =:= [] of true -> %% This constraint is constant. Solve it immediately. - case solve_one_c(C, dict:new()) of + case solve_one_c(C, dict:new(), []) of error -> throw(error); _ -> %% This is always true, keep it anyway for logistic reasons diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index fa9ad2eae2..d6ddf76956 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -47,6 +47,32 @@ -include("dialyzer.hrl"). +%%-define(DEBUG, true). + +-ifdef(DEBUG). +print_types(RecDict) -> + Keys = dict:fetch_keys(RecDict), + print_types1(Keys, 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(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(T, RecDict); +print_types1([{record, _Name} = Key|T], RecDict) -> + {ok, [{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_)). +-else. +-define(debug(D_), ok). +-endif. + %% %% Types that need to be imported from somewhere else %% @@ -61,13 +87,13 @@ %% ============================================================================ -spec get_abstract_code_from_src(atom() | file:filename()) -> - {'ok', abstract_code()} | {'error', [string()]}. + {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File) -> get_abstract_code_from_src(File, src_compiler_opts()). -spec get_abstract_code_from_src(atom() | file:filename(), comp_options()) -> - {'ok', abstract_code()} | {'error', [string()]}. + {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File, Opts) -> case compile:file(File, [to_pp, binary|Opts]) of @@ -137,60 +163,60 @@ get_core_from_abstract_code(AbstrCode, Opts) -> %% ============================================================================ -spec get_record_and_type_info(abstract_code()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'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(), atom(), dict()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'error', string()}. + +get_record_and_type_info(AbstractCode, Module, RecDict) -> + get_record_and_type_info(AbstractCode, Module, [], RecDict). get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], - Module, RecDict) -> - case get_record_fields(Fields0, RecDict) of - {ok, Fields} -> - Arity = length(Fields), - Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, - NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, NewRecDict); - {error, Error} -> - {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", - [Name, Error]))} - end; + Module, Records, RecDict) -> + {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, RecDict) -> + |Left], Module, Records, RecDict) -> %% This overrides the original record declaration. - case get_record_fields(Fields0, RecDict) of - {ok, Fields} -> - Arity = length(Fields), - Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, - NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, NewRecDict); - {error, Error} -> - {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", - [Name, Error]))} - end; + {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, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + 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, NewRecDict) + get_record_and_type_info(Left, Module, Records, NewRecDict) catch throw:{error, _} = Error -> Error end; get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], - Module, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + 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, NewRecDict) + get_record_and_type_info(Left, Module, Records, NewRecDict) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([_Other|Left], Module, RecDict) -> - get_record_and_type_info(Left, Module, RecDict); -get_record_and_type_info([], _Module, RecDict) -> - {ok, RecDict}. +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; + {Name, {error, Error}} -> + {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", + [Name, Error]))} + end. add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of @@ -198,7 +224,6 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> throw({error, io_lib:format("Type already defined: ~w\n", [Name])}); false -> ArgTypes = [erl_types:t_from_form(X) || X <- ArgForms], - _Type = erl_types:t_from_form(TypeForm, RecDict), case lists:all(fun erl_types:t_is_var/1, ArgTypes) of true -> ArgNames = [erl_types:t_var_name(X) || X <- ArgTypes], @@ -219,21 +244,36 @@ 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, - try - Type = erl_types:t_from_form(TypeForm, RecDict), - get_record_fields(Left, RecDict, [{Name, Type}|Acc]) - catch - throw:{error, _} = Error -> Error - end; + get_record_fields(Left, RecDict, [{Name, TypeForm}|Acc]); get_record_fields([{record_field, _Line, Name}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc], get_record_fields(Left, RecDict, NewAcc); get_record_fields([{record_field, _Line, Name, _Init}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|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, + {Name, Error} + end. + -spec process_record_remote_types(dialyzer_codeserver:codeserver()) -> dialyzer_codeserver:codeserver(). process_record_remote_types(CServer) -> @@ -269,7 +309,7 @@ merge_records(NewRecords, OldRecords) -> %% ============================================================================ -spec get_spec_info(module(), abstract_code(), dict()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'error', string()}. get_spec_info(ModName, AbstractCode, RecordsDict) -> get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile"). -- cgit v1.2.3