diff options
Diffstat (limited to 'lib/dialyzer/src')
-rw-r--r-- | lib/dialyzer/src/dialyzer.erl | 34 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer.hrl | 4 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_analysis_callgraph.erl | 142 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_behaviours.erl | 348 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl.erl | 70 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl_parse.erl | 9 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_codeserver.erl | 52 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_contracts.erl | 146 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_dataflow.erl | 146 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_gui.erl | 17 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_options.erl | 10 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_plt.erl | 74 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_succ_typings.erl | 24 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_typesig.erl | 3 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_utils.erl | 49 |
15 files changed, 663 insertions, 465 deletions
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index 5014a4244c..3e3c12405f 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -442,23 +442,29 @@ message_to_string({opaque_type_test, [Fun, Opaque]}) -> message_to_string({race_condition, [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_type_mismatch, [B, F, A, ST, CT]}) -> + io_lib:format("The inferred return type of ~w/~w (~s) has nothing in common" + " with ~s, which is the expected return type for the callback of" + " ~w behaviour\n", [F, A, ST, CT, B]); +message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> + io_lib:format("The inferred type for the ~s argument of ~w/~w (~s) is" + " not a supertype of ~s, which is expected type for this" + " argument in the callback of the ~w behaviour\n", + [ordinal(N), F, A, ST, CT, B]); +message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}) -> + io_lib:format("The return type ~s in the specification of ~w/~w is not a" + " subtype of ~s, which is the expected return type for the" + " callback of ~w behaviour\n", [ST, F, A, CT, B]); +message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> + io_lib:format("The specified type for the ~s argument of ~w/~w (~s) is" + " not a supertype of ~s, which is expected type for this" + " argument in the callback of the ~w behaviour\n", + [ordinal(N), F, A, ST, CT, 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]). +message_to_string({callback_info_missing, [B]}) -> + io_lib:format("Callback info about the ~w behaviour is not available\n", [B]). %%----------------------------------------------------------------------------- %% Auxiliary functions below diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index 9d2e554981..5e089d1773 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -57,6 +57,7 @@ -define(WARN_UNMATCHED_RETURN, warn_umatched_return). -define(WARN_RACE_CONDITION, warn_race_condition). -define(WARN_BEHAVIOUR, warn_behaviour). +-define(WARN_UNDEFINED_CALLBACK, warn_undefined_callbacks). %% %% The following type has double role: @@ -71,7 +72,8 @@ | ?WARN_CONTRACT_NOT_EQUAL | ?WARN_CONTRACT_SUBTYPE | ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH | ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION - | ?WARN_BEHAVIOUR | ?WARN_CONTRACT_RANGE. + | ?WARN_BEHAVIOUR | ?WARN_CONTRACT_RANGE + | ?WARN_UNDEFINED_CALLBACK. %% %% This is the representation of each warning as they will be returned diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index abad1f3a75..458f3a4c81 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -2,7 +2,7 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. 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 @@ -43,8 +43,7 @@ parent :: pid(), plt :: dialyzer_plt:plt(), start_from = byte_code :: start_from(), - use_contracts = true :: boolean(), - behaviours = {false,[]} :: {boolean(),[atom()]} + use_contracts = true :: boolean() }). -record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}). @@ -57,9 +56,7 @@ start(Parent, LegalWarnings, Analysis) -> RacesOn = ordsets:is_element(?WARN_RACE_CONDITION, LegalWarnings), - BehavOn = ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings), - Analysis0 = Analysis#analysis{race_detection = RacesOn, - behaviours_chk = BehavOn}, + Analysis0 = Analysis#analysis{race_detection = RacesOn}, Analysis1 = expand_files(Analysis0), Analysis2 = run_analysis(Analysis1), State = #server_state{parent = Parent, legal_warnings = LegalWarnings}, @@ -125,8 +122,7 @@ analysis_start(Parent, Analysis) -> plt = Plt, parent = Parent, start_from = Analysis#analysis.start_from, - use_contracts = Analysis#analysis.use_contracts, - behaviours = {Analysis#analysis.behaviours_chk, []} + use_contracts = Analysis#analysis.use_contracts }, Files = ordsets:from_list(Analysis#analysis.files), {Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State), @@ -180,8 +176,8 @@ analysis_start(Parent, Analysis) -> send_analysis_done(Parent, Plt4, State3#analysis_state.doc_plt). analyze_callgraph(Callgraph, State) -> - Plt = State#analysis_state.plt, Codeserver = State#analysis_state.codeserver, + Plt = dialyzer_plt:insert_callbacks(State#analysis_state.plt, Codeserver), Parent = State#analysis_state.parent, case State#analysis_state.analysis_type of plt_build -> @@ -192,13 +188,11 @@ 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, - BehavioursChk), + Codeserver, NoWarn, Parent), dialyzer_callgraph:delete(Callgraph1), send_warnings(State#analysis_state.parent, Warnings), State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} @@ -213,8 +207,7 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, include_dirs = Dirs, parent = Parent, use_contracts = UseContracts, - start_from = StartFrom, - behaviours = {BehChk, _} + start_from = StartFrom } = State) -> send_log(Parent, "Reading files and computing callgraph... "), {T1, _} = statistics(runtime), @@ -263,37 +256,26 @@ 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), - {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), + NewCallgraph2 = cleanup_callgraph(State, 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, - behaviours = {BehChk, KnownBehaviours} + codeserver = CodeServer }, 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, + RelevantAPICalls = + dialyzer_behaviours:get_behaviour_apis([gen_server]), + BehaviourAPICalls = [Call || {_From, To} = Call <- ExtCalls, + lists:member(To, RelevantAPICalls)], + Callgraph2 = + dialyzer_callgraph:put_behaviour_api_calls(BehaviourAPICalls, + Callgraph1), ExtCalls1 = [Call || Call = {_From, To} <- ExtCalls, not dialyzer_plt:contains_mfa(InitPlt, To)], {BadCalls1, RealExtCalls} = @@ -325,63 +307,42 @@ compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts) -> case dialyzer_utils:get_abstract_code_from_src(File, CompOpts) of {error, _Msg} = Error -> Error; {ok, AbstrCode} -> - case dialyzer_utils:get_core_from_abstract_code(AbstrCode, CompOpts) of - error -> {error, " Could not find abstract code for: " ++ File}; - {ok, Core} -> - Mod = cerl:concrete(cerl:module_name(Core)), - NoWarn = abs_get_nowarn(AbstrCode, Mod), - case dialyzer_utils:get_record_and_type_info(AbstrCode) of - {error, _} = Error -> Error; - {ok, RecInfo} -> - CServer2 = - dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), - case UseContracts of - true -> - case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of - {error, _} = Error -> Error; - {ok, SpecInfo} -> - CServer3 = - dialyzer_codeserver:store_temp_contracts(Mod, - SpecInfo, - CServer2), - store_core(Mod, Core, NoWarn, Callgraph, CServer3) - end; - false -> - store_core(Mod, Core, NoWarn, Callgraph, CServer2) - end - end - end + compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, UseContracts) end. compile_byte(File, Callgraph, CServer, UseContracts) -> case dialyzer_utils:get_abstract_code_from_beam(File) of error -> {error, " Could not get abstract code for: " ++ File ++ "\n" ++ - " Recompile with +debug_info or analyze starting from source code"}; + " Recompile with +debug_info or analyze starting from source code"}; {ok, AbstrCode} -> - case dialyzer_utils:get_core_from_abstract_code(AbstrCode) of - error -> {error, " Could not get core for: "++File}; - {ok, Core} -> - Mod = cerl:concrete(cerl:module_name(Core)), - NoWarn = abs_get_nowarn(AbstrCode, Mod), - case dialyzer_utils:get_record_and_type_info(AbstrCode) of - {error, _} = Error -> Error; - {ok, RecInfo} -> - CServer1 = - dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), - case UseContracts of - true -> - case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of - {error, _} = Error -> Error; - {ok, SpecInfo} -> - CServer2 = - dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo, - CServer1), - store_core(Mod, Core, NoWarn, Callgraph, CServer2) - end; - false -> - store_core(Mod, Core, NoWarn, Callgraph, CServer1) - end + compile_common(File, AbstrCode, [], Callgraph, CServer, UseContracts) + end. + +compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, UseContracts) -> + case dialyzer_utils:get_core_from_abstract_code(AbstrCode, CompOpts) of + error -> {error, " Could not get core Erlang code for: " ++ File}; + {ok, Core} -> + Mod = cerl:concrete(cerl:module_name(Core)), + NoWarn = abs_get_nowarn(AbstrCode, Mod), + case dialyzer_utils:get_record_and_type_info(AbstrCode) of + {error, _} = Error -> Error; + {ok, RecInfo} -> + CServer1 = + dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), + case UseContracts of + true -> + case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of + {error, _} = Error -> Error; + {ok, SpecInfo, CallbackInfo} -> + CServer2 = + dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo, + CallbackInfo, + CServer1), + store_core(Mod, Core, NoWarn, Callgraph, CServer2) + end; + false -> + store_core(Mod, Core, NoWarn, Callgraph, CServer1) end end end. @@ -398,8 +359,17 @@ store_core(Mod, Core, NoWarn, Callgraph, CServer) -> store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer3, NoWarn). abs_get_nowarn(Abs, M) -> - [{M, F, A} - || {attribute, _, compile, {nowarn_unused_function, {F, A}}} <- Abs]. + Opts = lists:flatten([C || {attribute, _, compile, C} <- Abs]), + Warn = erl_lint:bool_option(warn_unused_function, nowarn_unused_function, + true, Opts), + case Warn of + false -> + [{M, F, A} || {function, _, F, A, _} <- Abs]; % all functions + true -> + [{M, F, A} || + {nowarn_unused_function, FAs} <- Opts, + {F, A} <- lists:flatten([FAs])] + end. get_exported_types_from_core(Core) -> Attrs = cerl:module_attrs(Core), diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 47ce9ba6eb..e89c08df7d 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010. All Rights Reserved. +%% Copyright Ericsson AB 2010-2011. 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 @@ -30,7 +30,7 @@ -module(dialyzer_behaviours). --export([check_callbacks/4, get_behaviours/2, get_behaviour_apis/1, +-export([check_callbacks/4, get_behaviour_apis/1, translate_behaviour_api_call/5, translatable_behaviours/1, translate_callgraph/3]). @@ -51,12 +51,6 @@ %%-------------------------------------------------------------------- --spec get_behaviours([module()], dialyzer_codeserver:codeserver()) -> - {[behaviour()], [behaviour()]}. - -get_behaviours(Modules, Codeserver) -> - get_behaviours(Modules, Codeserver, [], []). - -spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], dialyzer_plt:plt(), dialyzer_codeserver:codeserver()) -> [dial_warning()]. @@ -69,12 +63,170 @@ check_callbacks(Module, Attrs, Plt, Codeserver) -> MFA = {Module,module_info,0}, {_Var,Code} = dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), File = get_file(cerl:get_ann(Code)), - State = #state{plt = Plt, codeserver = Codeserver, filename = File, - behlines = BehLines}, + State = #state{plt = Plt, filename = File, behlines = BehLines, + codeserver = Codeserver}, Warnings = get_warnings(Module, Behaviours, State), [add_tag_file_line(Module, W, State) || W <- Warnings] end. +%%-------------------------------------------------------------------- + +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) -> + NewAcc = check_behaviour(Module, Behaviour, State, Acc), + get_warnings(Module, Rest, State, NewAcc). + +check_behaviour(Module, Behaviour, #state{plt = Plt} = State, Acc) -> + case dialyzer_plt:lookup_callbacks(Plt, Behaviour) of + [] -> [{callback_info_missing, [Behaviour]}|Acc]; + Callbacks -> check_all_callbacks(Module, Behaviour, Callbacks, State, Acc) + end. + +check_all_callbacks(_Module, _Behaviour, [], _State, Acc) -> + Acc; +check_all_callbacks(Module, Behaviour, [Cb|Rest], + #state{plt = Plt, codeserver = Codeserver} = State, Acc) -> + {{Behaviour, Function, Arity}, + {{_BehFile, _BehLine}, Callback}} = Cb, + CbMFA = {Module, Function, Arity}, + CbReturnType = dialyzer_contracts:get_contract_return(Callback), + CbArgTypes = dialyzer_contracts:get_contract_args(Callback), + Records = + case dict:find(Module, dialyzer_codeserver:get_records(Codeserver)) of + {ok, V} -> V; + error -> dict:new() + end, + Acc0 = Acc, + Acc1 = + case dialyzer_plt:lookup(Plt, CbMFA) of + 'none' -> [{callback_missing, [Behaviour, Function, Arity]}|Acc0]; + {'value', RetArgTypes} -> + Acc00 = Acc0, + {ReturnType, ArgTypes} = RetArgTypes, + Acc01 = + case erl_types:t_is_subtype(ReturnType, CbReturnType) of + true -> Acc00; + false -> + case erl_types:t_is_none( + erl_types:t_inf(ReturnType, CbReturnType)) of + false -> Acc00; + true -> + [{callback_type_mismatch, + [Behaviour, Function, Arity, + erl_types:t_to_string(ReturnType, Records), + erl_types:t_to_string(CbReturnType, Records)]}|Acc00] + end + end, + Acc02 = + case erl_types:any_none( + erl_types:t_inf_lists(ArgTypes, CbArgTypes)) of + false -> Acc01; + true -> + find_mismatching_args(type, ArgTypes, CbArgTypes, Behaviour, + Function, Arity, Records, 1, Acc01) + end, + Acc02 + end, + Acc2 = + case dialyzer_codeserver:lookup_mfa_contract(CbMFA, Codeserver) of + 'error' -> Acc1; + {ok, {{File, Line}, Contract}} -> + Acc10 = Acc1, + SpecReturnType0 = dialyzer_contracts:get_contract_return(Contract), + SpecArgTypes0 = dialyzer_contracts:get_contract_args(Contract), + SpecReturnType = erl_types:subst_all_vars_to_any(SpecReturnType0), + SpecArgTypes = + [erl_types:subst_all_vars_to_any(ArgT0) || ArgT0 <- SpecArgTypes0], + Acc11 = + case erl_types:t_is_subtype(SpecReturnType, CbReturnType) of + true -> Acc10; + false -> + ExtraType = erl_types:t_subtract(SpecReturnType, CbReturnType), + [{callback_spec_type_mismatch, + [File, Line, Behaviour, Function, Arity, + erl_types:t_to_string(ExtraType, Records), + erl_types:t_to_string(CbReturnType, Records)]}|Acc10] + end, + Acc12 = + case erl_types:any_none( + erl_types:t_inf_lists(SpecArgTypes, CbArgTypes)) of + false -> Acc11; + true -> + find_mismatching_args({spec, File, Line}, SpecArgTypes, + CbArgTypes, Behaviour, Function, + Arity, Records, 1, Acc11) + end, + Acc12 + end, + NewAcc = Acc2, + check_all_callbacks(Module, Behaviour, Rest, State, NewAcc). + +find_mismatching_args(_, [], [], _Beh, _Function, _Arity, _Records, _N, Acc) -> + Acc; +find_mismatching_args(Kind, [Type|Rest], [CbType|CbRest], Behaviour, + Function, Arity, Records, N, Acc) -> + case erl_types:t_is_none(erl_types:t_inf(Type, CbType)) of + false -> + find_mismatching_args(Kind, Rest, CbRest, Behaviour, Function, + Arity, Records, N+1, Acc); + true -> + Info = + [Behaviour, Function, Arity, N, + erl_types:t_to_string(Type, Records), + erl_types:t_to_string(CbType, Records)], + NewAcc = + [case Kind of + type -> {callback_arg_type_mismatch, Info}; + {spec, File, Line} -> + {callback_spec_arg_type_mismatch, [File, Line | Info]} + end | Acc], + find_mismatching_args(Kind, Rest, CbRest, Behaviour, Function, + Arity, Records, N+1, NewAcc) + end. + +add_tag_file_line(_Module, {Tag, [B|_R]} = Warn, State) + when Tag =:= callback_missing; + Tag =:= callback_info_missing -> + {B, Line} = lists:keyfind(B, 1, State#state.behlines), + Category = + case Tag of + callback_missing -> ?WARN_BEHAVIOUR; + callback_info_missing -> ?WARN_UNDEFINED_CALLBACK + end, + {Category, {State#state.filename, Line}, Warn}; +add_tag_file_line(_Module, {Tag, [File, Line|R]}, _State) + when Tag =:= callback_spec_type_mismatch; + Tag =:= callback_spec_arg_type_mismatch -> + {?WARN_BEHAVIOUR, {File, Line}, {Tag, R}}; +add_tag_file_line(Module, {_Tag, [_B, Fun, Arity|_R]} = Warn, State) -> + {_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). + +%%----------------------------------------------------------------------------- + -spec translatable_behaviours(cerl:c_module()) -> behaviour_api_dict(). translatable_behaviours(Tree) -> @@ -133,182 +285,6 @@ translate_callgraph([{Behaviour,_}|Behaviours], Module, Callgraph) -> 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{codeserver = CServer} = State, Acc) -> - Records = dialyzer_codeserver:get_records(CServer), - ExpTypes = dialyzer_codeserver:get_exported_types(CServer), - case parse_spec(Spec, ExpTypes, 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, ExpTypes, 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, ExpTypes, - 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) -> diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index 8d61216b7a..04a0db890f 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -29,10 +29,6 @@ -module(dialyzer_cl). -%% Avoid warning for local function error/1 clashing with autoimported BIF. --compile({no_auto_import,[error/1]}). -%% Avoid warning for local function error/2 clashing with autoimported BIF. --compile({no_auto_import,[error/2]}). -export([start/1]). -include("dialyzer.hrl"). @@ -88,7 +84,7 @@ init_opts_for_build(Opts) -> Plts -> Msg = io_lib:format("Could not build multiple PLT files: ~s\n", [format_plts(Plts)]), - error(Msg) + cl_error(Msg) end; false -> Opts#options{init_plts = []} end. @@ -110,7 +106,7 @@ init_opts_for_add(Opts) -> Plts -> Msg = io_lib:format("Could not add to multiple PLT files: ~s\n", [format_plts(Plts)]), - error(Msg) + cl_error(Msg) end; false -> case Opts#options.init_plts =:= [] of @@ -134,11 +130,12 @@ check_plt_aux([_] = Plt, Opts) -> report_check(Opts2), plt_common(Opts2, [], []); check_plt_aux([Plt|Plts], Opts) -> - Opts1 = Opts#options{init_plts = [Plt]}, - Opts2 = init_opts_for_check(Opts1), - report_check(Opts2), - plt_common(Opts2, [], []), - check_plt_aux(Plts, Opts). + case check_plt_aux([Plt], Opts) of + {?RET_NOTHING_SUSPICIOUS, []} -> check_plt_aux(Plts, Opts); + {?RET_DISCREPANCIES, Warns} -> + {_RET, MoreWarns} = check_plt_aux(Plts, Opts), + {?RET_DISCREPANCIES, Warns ++ MoreWarns} + end. init_opts_for_check(Opts) -> InitPlt = @@ -175,7 +172,7 @@ init_opts_for_remove(Opts) -> Plts -> Msg = io_lib:format("Could not remove from multiple PLT files: ~s\n", [format_plts(Plts)]), - error(Msg) + cl_error(Msg) end; false -> case Opts#options.init_plts =:= [] of @@ -193,7 +190,7 @@ plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) -> none -> ok; OutPlt -> {ok, Binary} = file:read_file(InitPlt), - file:write_file(OutPlt, Binary) + ok = file:write_file(OutPlt, Binary) end, case Opts#options.report_mode of quiet -> ok; @@ -221,19 +218,19 @@ plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) -> {error, no_such_file} -> Msg = io_lib:format("Could not find the PLT: ~s\n~s", [InitPlt, default_plt_error_msg()]), - error(Msg); + cl_error(Msg); {error, not_valid} -> Msg = io_lib:format("The file: ~s is not a valid PLT file\n~s", [InitPlt, default_plt_error_msg()]), - error(Msg); + cl_error(Msg); {error, read_error} -> Msg = io_lib:format("Could not read the PLT: ~s\n~s", [InitPlt, default_plt_error_msg()]), - error(Msg); + cl_error(Msg); {error, {no_file_to_remove, F}} -> Msg = io_lib:format("Could not remove the file ~s from the PLT: ~s\n", [F, InitPlt]), - error(Msg) + cl_error(Msg) end. default_plt_error_msg() -> @@ -426,7 +423,7 @@ assert_writable(PltFile) -> true -> ok; false -> Msg = io_lib:format(" The PLT file ~s is not writable", [PltFile]), - error(Msg) + cl_error(Msg) end. check_if_writable(PltFile) -> @@ -525,10 +522,7 @@ native_compile(Mods) -> end. hc(Mod) -> - case code:ensure_loaded(Mod) of - {module, Mod} -> ok; - {error, sticky_directory} -> ok - end, + {module, Mod} = code:ensure_loaded(Mod), case code:is_module_native(Mod) of true -> ok; false -> @@ -553,7 +547,7 @@ init_output(State0, #options{output_file = OutFile, {error, Reason} -> Msg = io_lib:format("Could not open output file ~p, Reason: ~p\n", [OutFile, Reason]), - error(State, lists:flatten(Msg)) + cl_error(State, lists:flatten(Msg)) end end. @@ -599,10 +593,10 @@ cl_loop(State, LogCache) -> cl_loop(NewState, LogCache); {'EXIT', BackendPid, {error, Reason}} -> Msg = failed_anal_msg(Reason, LogCache), - error(State, Msg); + cl_error(State, Msg); {'EXIT', BackendPid, Reason} when Reason =/= 'normal' -> Msg = failed_anal_msg(io_lib:format("~P", [Reason, 12]), LogCache), - error(State, Msg); + cl_error(State, Msg); _Other -> %% io:format("Received ~p\n", [_Other]), cl_loop(State, LogCache) @@ -611,7 +605,7 @@ cl_loop(State, LogCache) -> -spec failed_anal_msg(string(), [_]) -> nonempty_string(). failed_anal_msg(Reason, LogCache) -> - Msg = "Analysis failed with error: " ++ Reason ++ "\n", + Msg = "Analysis failed with error:\n" ++ Reason ++ "\n", case LogCache =:= [] of true -> Msg; false -> @@ -635,14 +629,14 @@ store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) -> store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) -> St#cl_state{unknown_behaviours = Beh ++ Behs}. --spec error(string()) -> no_return(). +-spec cl_error(string()) -> no_return(). -error(Msg) -> +cl_error(Msg) -> throw({dialyzer_error, Msg}). --spec error(#cl_state{}, string()) -> no_return(). +-spec cl_error(#cl_state{}, string()) -> no_return(). -error(State, Msg) -> +cl_error(State, Msg) -> case State#cl_state.output of standard_io -> ok; Outfile -> io:format(Outfile, "\n~s\n", [Msg]) @@ -754,15 +748,13 @@ print_unknown_behaviours(#cl_state{output = Output, 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 + {Prompt, Prefix} = + case Format of + formatted -> {"Unknown behaviours:\n"," "}; + raw -> {"%% Unknown behaviours:\n","%% "} + end, + io:put_chars(Output, Prompt), + do_print_unknown_behaviours(Output, Behaviours, Prefix) end. do_print_unknown_behaviours(Output, [B|T], Before) -> diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index f80eb81ac6..ff8fc39a5e 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -491,6 +491,12 @@ warning_options_msg() -> Suppress warnings for patterns that are unused or cannot match. -Wno_opaque Suppress warnings for violations of opaqueness of data types. + -Wno_behaviours + Suppress warnings about behaviour callbacks which drift from the published + recommended interfaces. + -Wno_undefined_callbacks + Suppress warnings about behaviours that have no -callback attributes for + their callbacks. -Wunmatched_returns *** Include warnings for function calls which ignore a structured return value or do not match against one of many possible return value(s). @@ -498,9 +504,6 @@ 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 b2097f7e53..13ca65e4dd 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. 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 @@ -28,10 +28,11 @@ -module(dialyzer_codeserver). -export([delete/1, - finalize_contracts/2, + finalize_contracts/3, finalize_exported_types/2, finalize_records/2, get_contracts/1, + get_callbacks/1, get_exported_types/1, get_exports/1, get_records/1, @@ -54,7 +55,7 @@ store_records/3, store_temp_records/3, store_contracts/3, - store_temp_contracts/3]). + store_temp_contracts/4]). -export_type([codeserver/0]). @@ -70,7 +71,10 @@ records = dict:new() :: dict(), temp_records = dict:new() :: dict(), contracts = dict:new() :: dict(), - temp_contracts = dict:new() :: dict()}). + callbacks = dict:new() :: dict(), + temp_contracts = dict:new() :: dict(), + temp_callbacks = dict:new() :: dict() + }). -opaque codeserver() :: #codeserver{}. @@ -227,24 +231,42 @@ lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) -> get_contracts(#codeserver{contracts = ContDict}) -> ContDict. --spec store_temp_contracts(atom(), dict(), codeserver()) -> codeserver(). +-spec get_callbacks(codeserver()) -> dict(). -store_temp_contracts(Mod, Dict, #codeserver{temp_contracts = C} = CS) +get_callbacks(#codeserver{callbacks = CallbDict}) -> + CallbDict. + +-spec store_temp_contracts(atom(), dict(), dict(), codeserver()) -> + codeserver(). + +store_temp_contracts(Mod, SpecDict, CallbackDict, + #codeserver{temp_contracts = Cn, + temp_callbacks = Cb} = CS) when is_atom(Mod) -> - case dict:size(Dict) =:= 0 of - true -> CS; - false -> CS#codeserver{temp_contracts = dict:store(Mod, Dict, C)} + CS1 = + case dict:size(SpecDict) =:= 0 of + true -> CS; + false -> CS#codeserver{temp_contracts = dict:store(Mod, SpecDict, Cn)} + end, + case dict:size(CallbackDict) =:= 0 of + true -> CS1; + false -> CS1#codeserver{temp_callbacks = dict:store(Mod, CallbackDict, Cb)} end. --spec get_temp_contracts(codeserver()) -> dict(). +-spec get_temp_contracts(codeserver()) -> {dict(), dict()}. -get_temp_contracts(#codeserver{temp_contracts = TempContDict}) -> - TempContDict. +get_temp_contracts(#codeserver{temp_contracts = TempContDict, + temp_callbacks = TempCallDict}) -> + {TempContDict, TempCallDict}. --spec finalize_contracts(dict(), codeserver()) -> codeserver(). +-spec finalize_contracts(dict(), dict(), codeserver()) -> codeserver(). -finalize_contracts(Dict, CS) -> - CS#codeserver{contracts = Dict, temp_contracts = dict:new()}. +finalize_contracts(CnDict, CbDict, CS) -> + CS#codeserver{contracts = CnDict, + callbacks = CbDict, + temp_contracts = dict:new(), + temp_callbacks = 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 84b926a17a..2b78b736ab 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -141,7 +141,8 @@ sequence([H|T], Delimiter) -> H ++ Delimiter ++ sequence(T, Delimiter). dialyzer_codeserver:codeserver(). process_contract_remote_types(CodeServer) -> - TmpContractDict = dialyzer_codeserver:get_temp_contracts(CodeServer), + {TmpContractDict, TmpCallbackDict} = + dialyzer_codeserver:get_temp_contracts(CodeServer), ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer), RecordDict = dialyzer_codeserver:get_records(CodeServer), ContractFun = @@ -155,7 +156,9 @@ process_contract_remote_types(CodeServer) -> dict:map(ContractFun, ContractDict) end, NewContractDict = dict:map(ModuleFun, TmpContractDict), - dialyzer_codeserver:finalize_contracts(NewContractDict, CodeServer). + NewCallbackDict = dict:map(ModuleFun, TmpCallbackDict), + dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict, + CodeServer). -spec check_contracts([{mfa(), file_contract()}], dialyzer_callgraph:callgraph(), dict()) -> plt_contracts(). @@ -253,7 +256,7 @@ check_extraneous([C|Cs], SuccType) -> 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]), + ?debug("CR = ~p\nSR = ~p\n", [CRngs, STRng]), case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of [] -> ok; CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}} @@ -349,28 +352,37 @@ insert_constraints([], Dict) -> Dict. store_tmp_contract(MFA, FileLine, TypeSpec, SpecDict, RecordsDict) -> %% io:format("contract from form: ~p\n", [TypeSpec]), - TmpContract = contract_from_form(TypeSpec, RecordsDict), + TmpContract = contract_from_form(TypeSpec, RecordsDict, FileLine), %% io:format("contract: ~p\n", [Contract]), dict:store(MFA, {FileLine, TmpContract}, SpecDict). -contract_from_form(Forms, RecDict) -> - {CFuns, Forms1} = contract_from_form(Forms, RecDict, [], []), +contract_from_form(Forms, RecDict, FileLine) -> + {CFuns, Forms1} = contract_from_form(Forms, RecDict, FileLine, [], []), #tmp_contract{contract_funs = CFuns, forms = Forms1}. contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict, - TypeAcc, FormAcc) -> + FileLine, TypeAcc, FormAcc) -> TypeFun = fun(ExpTypes, AllRecords) -> - Type = erl_types:t_from_form(Form, RecDict), + Type = + try + erl_types:t_from_form(Form, RecDict) + catch + throw:{error, Msg} -> + {File, Line} = FileLine, + NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File), + Line, Msg]), + throw({error, NewMsg}) + end, NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords), {NewType, []} end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, []} | FormAcc], - contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc); + contract_from_form(Left, RecDict, FileLine, NewTypeAcc, NewFormAcc); contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], - RecDict, TypeAcc, FormAcc) -> + RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = fun(ExpTypes, AllRecords) -> Constr1 = [constraint_from_form(C, RecDict, ExpTypes, AllRecords) @@ -382,8 +394,8 @@ contract_from_form([{type, _L1, bounded_fun, end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, Constr} | FormAcc], - contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc); -contract_from_form([], _RecDict, TypeAcc, FormAcc) -> + contract_from_form(Left, RecDict, FileLine, NewTypeAcc, NewFormAcc); +contract_from_form([], _RecDict, _FileLine, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. constraint_from_form({type, _, constraint, [{atom, _, is_subtype}, @@ -441,7 +453,21 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], {error, invalid_contract} -> [invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc]; {error, {extra_range, ExtraRanges, STRange}} -> - [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; + Warn = + case t_from_forms_without_remote(Contract#contract.forms, + RecDict) of + {ok, NoRemoteType} -> + CRet = erl_types:t_fun_range(NoRemoteType), + erl_types:t_is_subtype(ExtraRanges, CRet); + unsupported -> + true + end, + case Warn of + true -> + [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; + false -> + Acc + end; {error, Msg} -> [{?WARN_CONTRACT_SYNTAX, FileLine, Msg}|Acc]; ok -> @@ -504,26 +530,92 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) -> extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) -> SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)), ContractString0 = lists:flatten(dialyzer_utils:format_sig(CSig, RecDict)), - case SigString =:= ContractString0 of + %% The only difference is in record fields containing 'undefined' or not. + IsUndefRecordFieldsRelated = SigString =:= ContractString0, + {IsRemoteTypesRelated, SubtypeRelation} = + is_remote_types_related(Contract, CSig, Sig, RecDict), + case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of true -> - %% The only difference is in record fields containing 'undefined' or not. no_warning; false -> ContractString = contract_to_string(Contract), {Tag, Msg} = - case erl_types:t_is_subtype(CSig, Sig) of - true -> + case SubtypeRelation of + contract_is_subtype -> {?WARN_CONTRACT_SUBTYPE, {contract_subtype, [M, F, A, ContractString, SigString]}}; - false -> - case erl_types:t_is_subtype(Sig, CSig) of - true -> - {?WARN_CONTRACT_SUPERTYPE, - {contract_supertype, [M, F, A, ContractString, SigString]}}; - false -> - {?WARN_CONTRACT_NOT_EQUAL, - {contract_diff, [M, F, A, ContractString, SigString]}} - end + contract_is_supertype -> + {?WARN_CONTRACT_SUPERTYPE, + {contract_supertype, [M, F, A, ContractString, SigString]}}; + neither -> + {?WARN_CONTRACT_NOT_EQUAL, + {contract_diff, [M, F, A, ContractString, SigString]}} end, {warning, {Tag, FileLine, Msg}} end. + +is_remote_types_related(Contract, CSig, Sig, RecDict) -> + case erl_types:t_is_subtype(CSig, Sig) of + true -> + {false, contract_is_subtype}; + false -> + case erl_types:t_is_subtype(Sig, CSig) of + true -> + case t_from_forms_without_remote(Contract#contract.forms, RecDict) of + {ok, NoRemoteTypeSig} -> + case blame_remote(CSig, NoRemoteTypeSig, Sig) of + true -> + {true, neither}; + false -> + {false, contract_is_supertype} + end; + unsupported -> + {false, contract_is_supertype} + end; + false -> + {false, neither} + end + end. + +t_from_forms_without_remote([{FType, []}], RecDict) -> + Type0 = erl_types:t_from_form(FType, RecDict), + Map = + fun(Type) -> + case erl_types:t_is_remote(Type) of + true -> erl_types:t_none(); + false -> Type + end + end, + {ok, erl_types:t_map(Map, Type0)}; +t_from_forms_without_remote([{_FType, _Constrs}], _RecDict) -> + %% 'When' constraints + unsupported; +t_from_forms_without_remote(_Forms, _RecDict) -> + %% Lots of forms + unsupported. + +blame_remote(ContractSig, NoRemoteContractSig, Sig) -> + CArgs = erl_types:t_fun_args(ContractSig), + CRange = erl_types:t_fun_range(ContractSig), + NRArgs = erl_types:t_fun_args(NoRemoteContractSig), + NRRange = erl_types:t_fun_range(NoRemoteContractSig), + SArgs = erl_types:t_fun_args(Sig), + SRange = erl_types:t_fun_range(Sig), + blame_remote_list([CRange|CArgs], [NRRange|NRArgs], [SRange|SArgs]). + +blame_remote_list([], [], []) -> + true; +blame_remote_list([CArg|CArgs], [NRArg|NRArgs], [SArg|SArgs]) -> + case erl_types:t_is_equal(CArg, NRArg) of + true -> + case not erl_types:t_is_equal(CArg, SArg) of + true -> false; + false -> blame_remote_list(CArgs, NRArgs, SArgs) + end; + false -> + case erl_types:t_is_subtype(SArg, NRArg) + andalso not erl_types:t_is_subtype(NRArg, SArg) of + true -> false; + false -> blame_remote_list(CArgs, NRArgs, SArgs) + end + end. diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index d74c04385b..6008dba080 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -101,6 +101,12 @@ behaviour_api_dict = [] :: dialyzer_behaviours:behaviour_api_dict()}). +-record(map, {dict = dict:new() :: dict(), + subst = dict:new() :: dict(), + modified = [] :: [Key :: term()], + modified_stack = [] :: [{[Key :: term()],reference()}], + ref = undefined :: reference() | undefined}). + %% Exported Types -opaque state() :: #state{}. @@ -1058,12 +1064,13 @@ handle_case(Tree, Map, #state{callgraph = Callgraph} = State) -> RaceListSize + 1, State1); false -> State1 end, + Map2 = join_maps_begin(Map1), {MapList, State3, Type} = handle_clauses(Clauses, Arg, ArgType, ArgType, State2, - [], Map1, [], []), - Map2 = join_maps(MapList, Map1), + [], Map2, [], []), + Map3 = join_maps_end(MapList, Map2), debug_pp_map(Map2), - {State3, Map2, Type} + {State3, Map3, Type} end. %%---------------------------------------- @@ -1640,14 +1647,15 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> false -> SubTuples = t_tuple_subtypes(Tuple), %% Need to call the top function to get the try-catch wrapper + MapJ = join_maps_begin(Map), Results = case Rev of true -> [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [], - Map, State) + MapJ, State) || SubTuple <- SubTuples]; false -> - [bind_pat_vars(Es, t_tuple_args(SubTuple), [], Map, State) + [bind_pat_vars(Es, t_tuple_args(SubTuple), [], MapJ, State) || SubTuple <- SubTuples] end, case lists:keyfind(opaque, 2, Results) of @@ -1661,7 +1669,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> false -> bind_error([Pat], Tuple, t_none(), bind) end; Maps -> - Map1 = join_maps(Maps, Map), + Map1 = join_maps_end(Maps, MapJ), TupleType = t_sup([t_tuple(EsTypes) || {M, EsTypes} <- Results, M =/= error]), {Map1, TupleType} @@ -2308,27 +2316,29 @@ handle_guard_and(Guard, Map, Env, Eval, State) -> end end; neg -> + MapJ = join_maps_begin(Map), {Map1, Type1} = - try bind_guard(Arg1, Map, Env, neg, State) - catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State) + try bind_guard(Arg1, MapJ, Env, neg, State) + catch throw:{fail, _} -> bind_guard(Arg2, MapJ, Env, pos, State) end, {Map2, Type2} = - try bind_guard(Arg2, Map, Env, neg, State) - catch throw:{fail, _} -> bind_guard(Arg1, Map, Env, pos, State) + try bind_guard(Arg2, MapJ, Env, neg, State) + catch throw:{fail, _} -> bind_guard(Arg1, MapJ, Env, pos, State) end, case t_is_atom(false, Type1) orelse t_is_atom(false, Type2) of - true -> {join_maps([Map1, Map2], Map), t_atom(false)}; + true -> {join_maps_end([Map1, Map2], MapJ), t_atom(false)}; false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State) end; dont_know -> - {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), - {Map2, Type2} = bind_guard(Arg2, Map, Env, dont_know, State), + MapJ = join_maps_begin(Map), + {Map1, Type1} = bind_guard(Arg1, MapJ, Env, dont_know, State), + {Map2, Type2} = bind_guard(Arg2, MapJ, Env, dont_know, State), Bool1 = t_inf(Type1, t_boolean()), Bool2 = t_inf(Type2, t_boolean()), case t_is_none(Bool1) orelse t_is_none(Bool2) of true -> throw({fatal_fail, none}); false -> - NewMap = join_maps([Map1, Map2], Map), + NewMap = join_maps_end([Map1, Map2], MapJ), NewType = case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of {['true'] , ['true'] } -> t_atom(true); @@ -2344,20 +2354,21 @@ handle_guard_or(Guard, Map, Env, Eval, State) -> [Arg1, Arg2] = cerl:call_args(Guard), case Eval of pos -> + MapJ = join_maps_begin(Map), {Map1, Bool1} = - try bind_guard(Arg1, Map, Env, pos, State) + try bind_guard(Arg1, MapJ, Env, pos, State) catch - throw:{fail,_} -> bind_guard(Arg1, Map, Env, dont_know, State) + throw:{fail,_} -> bind_guard(Arg1, MapJ, Env, dont_know, State) end, {Map2, Bool2} = - try bind_guard(Arg2, Map, Env, pos, State) + try bind_guard(Arg2, MapJ, Env, pos, State) catch - throw:{fail,_} -> bind_guard(Arg2, Map, Env, dont_know, State) + throw:{fail,_} -> bind_guard(Arg2, MapJ, Env, dont_know, State) end, case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2)) orelse (t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of - true -> {join_maps([Map1, Map2], Map), t_atom(true)}; + true -> {join_maps_end([Map1, Map2], MapJ), t_atom(true)}; false -> signal_guard_fail(Eval, Guard, [Bool1, Bool2], State) end; neg -> @@ -2372,14 +2383,15 @@ handle_guard_or(Guard, Map, Env, Eval, State) -> end end; dont_know -> - {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), - {Map2, Type2} = bind_guard(Arg2, Map, Env, dont_know, State), + MapJ = join_maps_begin(Map), + {Map1, Type1} = bind_guard(Arg1, MapJ, Env, dont_know, State), + {Map2, Type2} = bind_guard(Arg2, MapJ, Env, dont_know, State), Bool1 = t_inf(Type1, t_boolean()), Bool2 = t_inf(Type2, t_boolean()), case t_is_none(Bool1) orelse t_is_none(Bool2) of true -> throw({fatal_fail, none}); false -> - NewMap = join_maps([Map1, Map2], Map), + NewMap = join_maps_end([Map1, Map2], MapJ), NewType = case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of {['false'], ['false']} -> t_atom(false); @@ -2493,8 +2505,9 @@ mk_guard_msg(Eval, F, Args, ArgTypes, State) -> end end. -bind_guard_case_clauses(Arg, Clauses, Map, Env, Eval, State) -> +bind_guard_case_clauses(Arg, Clauses, Map0, Env, Eval, State) -> Clauses1 = filter_fail_clauses(Clauses), + Map = join_maps_begin(Map0), {GenMap, GenArgType} = bind_guard(Arg, Map, Env, dont_know, State), bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval, t_none(), [], State). @@ -2594,7 +2607,7 @@ bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, AccType, AccMaps, _State) -> case t_is_none(AccType) of true -> throw({fail, none}); - false -> {join_maps(AccMaps, Map), AccType} + false -> {join_maps_end(AccMaps, Map), AccType} end. %%% =========================================================================== @@ -2604,11 +2617,34 @@ bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, %%% =========================================================================== map__new() -> - {dict:new(), dict:new()}. + #map{}. + +%% join_maps_begin pushes 'modified' to the stack; join_maps pops +%% 'modified' from the stack. + +join_maps_begin(#map{modified = M, modified_stack = S, ref = Ref} = Map) -> + Map#map{ref = make_ref(), modified = [], modified_stack = [{M,Ref} | S]}. + +join_maps_end(Maps, MapOut) -> + #map{ref = Ref, modified_stack = [{M1,R1} | S]} = MapOut, + true = lists:all(fun(M) -> M#map.ref =:= Ref end, Maps), % sanity + Keys0 = lists:usort(lists:append([M#map.modified || M <- Maps])), + #map{dict = Dict, subst = Subst} = MapOut, + Keys = [Key || + Key <- Keys0, + dict:is_key(Key, Dict) orelse dict:is_key(Key, Subst)], + Out = case Maps of + [] -> join_maps(Maps, MapOut); + _ -> join_maps(Keys, Maps, MapOut) + end, + debug_join_check(Maps, MapOut, Out), + Out#map{ref = R1, + modified = Out#map.modified ++ M1, % duplicates possible + modified_stack = S}. join_maps(Maps, MapOut) -> - {Map, Subst} = MapOut, - Keys = ordsets:from_list(dict:fetch_keys(Map) ++ dict:fetch_keys(Subst)), + #map{dict = Dict, subst = Subst} = MapOut, + Keys = ordsets:from_list(dict:fetch_keys(Dict) ++ dict:fetch_keys(Subst)), join_maps(Keys, Maps, MapOut). join_maps([Key|Left], Maps, MapOut) -> @@ -2631,6 +2667,17 @@ join_maps_one_key([Map|Left], Key, AccType) -> join_maps_one_key([], _Key, AccType) -> AccType. +-ifdef(DEBUG). +debug_join_check(Maps, MapOut, Out) -> + #map{dict = Dict, subst = Subst} = Out, + #map{dict = Dict2, subst = Subst2} = join_maps(Maps, MapOut), + F = fun(D) -> lists:keysort(1, dict:to_list(D)) end, + [throw({bug, join_maps}) || + F(Dict) =/= F(Dict2) orelse F(Subst) =/= F(Subst2)]. +-else. +debug_join_check(_Maps, _MapOut, _Out) -> ok. +-endif. + enter_type_lists([Key|KeyTail], [Val|ValTail], Map) -> Map1 = enter_type(Key, Val, Map), enter_type_lists(KeyTail, ValTail, Map1); @@ -2643,20 +2690,21 @@ enter_type_list([{Key, Val}|Left], Map) -> enter_type_list([], Map) -> Map. -enter_type(Key, Val, {Map, Subst} = MS) -> +enter_type(Key, Val, MS) -> case cerl:is_literal(Key) of true -> MS; false -> case cerl:is_c_values(Key) of true -> - Keys = cerl:values_es(Key), + Keys = cerl:values_es(Key), case t_is_any(Val) orelse t_is_none(Val) of true -> enter_type_lists(Keys, [Val || _ <- Keys], MS); false -> - enter_type_lists(cerl:values_es(Key), t_to_tlist(Val), MS) + enter_type_lists(Keys, t_to_tlist(Val), MS) end; false -> + #map{dict = Dict, subst = Subst} = MS, KeyLabel = get_label(Key), case dict:find(KeyLabel, Subst) of {ok, NewKey} -> @@ -2664,21 +2712,25 @@ enter_type(Key, Val, {Map, Subst} = MS) -> enter_type(NewKey, Val, MS); error -> ?debug("Entering ~p :: ~s\n", [KeyLabel, t_to_string(Val)]), - case dict:find(KeyLabel, Map) of + case dict:find(KeyLabel, Dict) of {ok, Val} -> MS; - {ok, _OldVal} -> {dict:store(KeyLabel, Val, Map), Subst}; - error -> {dict:store(KeyLabel, Val, Map), Subst} + {ok, _OldVal} -> store_map(KeyLabel, Val, MS); + error -> store_map(KeyLabel, Val, MS) end end end end. -enter_subst(Key, Val, {Map, Subst} = MS) -> +store_map(Key, Val, #map{dict = Dict, ref = undefined} = Map) -> + Map#map{dict = dict:store(Key, Val, Dict)}; +store_map(Key, Val, #map{dict = Dict, modified = Mod} = Map) -> + Map#map{dict = dict:store(Key, Val, Dict), modified = [Key | Mod]}. + +enter_subst(Key, Val, #map{subst = Subst} = MS) -> KeyLabel = get_label(Key), case cerl:is_literal(Val) of true -> - NewMap = dict:store(KeyLabel, literal_type(Val), Map), - {NewMap, Subst}; + store_map(KeyLabel, literal_type(Val), MS); false -> case cerl:is_c_var(Val) of false -> MS; @@ -2691,25 +2743,29 @@ enter_subst(Key, Val, {Map, Subst} = MS) -> if KeyLabel =:= ValLabel -> MS; true -> ?debug("Subst: storing ~p = ~p\n", [KeyLabel, ValLabel]), - NewSubst = dict:store(KeyLabel, ValLabel, Subst), - {Map, NewSubst} + store_subst(KeyLabel, ValLabel, MS) end end end end. -lookup_type(Key, {Map, Subst}) -> - lookup(Key, Map, Subst, t_none()). +store_subst(Key, Val, #map{subst = S, ref = undefined} = Map) -> + Map#map{subst = dict:store(Key, Val, S)}; +store_subst(Key, Val, #map{subst = S, modified = Mod} = Map) -> + Map#map{subst = dict:store(Key, Val, S), modified = [Key | Mod]}. + +lookup_type(Key, #map{dict = Dict, subst = Subst}) -> + lookup(Key, Dict, Subst, t_none()). -lookup(Key, Map, Subst, AnyNone) -> +lookup(Key, Dict, Subst, AnyNone) -> case cerl:is_literal(Key) of true -> literal_type(Key); false -> Label = get_label(Key), case dict:find(Label, Subst) of - {ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone); + {ok, NewKey} -> lookup(NewKey, Dict, Subst, AnyNone); error -> - case dict:find(Label, Map) of + case dict:find(Label, Dict) of {ok, Val} -> Val; error -> AnyNone end @@ -2744,8 +2800,8 @@ mark_as_fresh([], Map) -> Map. -ifdef(DEBUG). -debug_pp_map(Map = {Map0, _Subst}) -> - Keys = dict:fetch_keys(Map0), +debug_pp_map(#map{dict = Dict}=Map) -> + Keys = dict:fetch_keys(Dict), io:format("Map:\n", []), lists:foreach(fun (Key) -> io:format("\t~w :: ~s\n", diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl index ccd80a4835..f60194e01f 100644 --- a/lib/dialyzer/src/dialyzer_gui.erl +++ b/lib/dialyzer/src/dialyzer_gui.erl @@ -28,6 +28,23 @@ %%%----------------------------------------------------------------------- -module(dialyzer_gui). +-compile([{nowarn_deprecated_function,{gs,button,2}}, + {nowarn_deprecated_function,{gs,config,2}}, + {nowarn_deprecated_function,{gs,destroy,1}}, + {nowarn_deprecated_function,{gs,editor,2}}, + {nowarn_deprecated_function,{gs,entry,2}}, + {nowarn_deprecated_function,{gs,frame,2}}, + {nowarn_deprecated_function,{gs,label,2}}, + {nowarn_deprecated_function,{gs,listbox,2}}, + {nowarn_deprecated_function,{gs,menu,2}}, + {nowarn_deprecated_function,{gs,menubar,2}}, + {nowarn_deprecated_function,{gs,menubutton,2}}, + {nowarn_deprecated_function,{gs,menuitem,2}}, + {nowarn_deprecated_function,{gs,radiobutton,2}}, + {nowarn_deprecated_function,{gs,read,2}}, + {nowarn_deprecated_function,{gs,start,0}}, + {nowarn_deprecated_function,{gs,stop,0}}, + {nowarn_deprecated_function,{gs,window,2}}]). -export([start/1]). diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index b2a67de8bd..866650a0b2 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -49,7 +49,9 @@ build(Opts) -> ?WARN_CALLGRAPH, ?WARN_CONTRACT_RANGE, ?WARN_CONTRACT_TYPES, - ?WARN_CONTRACT_SYNTAX], + ?WARN_CONTRACT_SYNTAX, + ?WARN_BEHAVIOUR, + ?WARN_UNDEFINED_CALLBACK], DefaultWarns1 = ordsets:from_list(DefaultWarns), InitPlt = dialyzer_plt:get_default_plt(), DefaultOpts = #options{}, @@ -275,14 +277,16 @@ build_warnings([Opt|Opts], Warnings) -> no_contracts -> Warnings1 = ordsets:del_element(?WARN_CONTRACT_SYNTAX, Warnings), ordsets:del_element(?WARN_CONTRACT_TYPES, Warnings1); + no_behaviours -> + ordsets:del_element(?WARN_BEHAVIOUR, Warnings); + no_undefined_callbacks -> + ordsets:del_element(?WARN_UNDEFINED_CALLBACK, Warnings); unmatched_returns -> ordsets:add_element(?WARN_UNMATCHED_RETURN, Warnings); error_handling -> 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 6033d7f17c..206c43e4e2 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -43,10 +43,12 @@ %% insert/3, insert_list/2, insert_contract_list/2, + insert_callbacks/2, insert_types/2, insert_exported_types/2, lookup/2, lookup_contract/2, + lookup_callbacks/2, lookup_module/2, merge_plts/1, merge_plts_or_report_conflicts/2, @@ -79,6 +81,7 @@ -record(plt, {info = table_new() :: dict(), types = table_new() :: dict(), contracts = table_new() :: dict(), + callbacks = table_new() :: dict(), exported_types = sets:new() :: set()}). -opaque plt() :: #plt{}. @@ -91,6 +94,7 @@ file_md5_list = [] :: [file_md5()], info = dict:new() :: dict(), contracts = dict:new() :: dict(), + callbacks = dict:new() :: dict(), types = dict:new() :: dict(), exported_types = sets:new() :: set(), mod_deps :: mod_deps(), @@ -105,20 +109,26 @@ new() -> -spec delete_module(plt(), atom()) -> plt(). -delete_module(#plt{info = Info, types = Types, contracts = Contracts, +delete_module(#plt{info = Info, types = Types, + contracts = Contracts, + callbacks = Callbacks, exported_types = ExpTypes}, Mod) -> #plt{info = table_delete_module(Info, Mod), types = table_delete_module2(Types, Mod), contracts = table_delete_module(Contracts, Mod), + callbacks = table_delete_module(Callbacks, Mod), exported_types = table_delete_module1(ExpTypes, Mod)}. -spec delete_list(plt(), [mfa() | integer()]) -> plt(). -delete_list(#plt{info = Info, types = Types, contracts = Contracts, +delete_list(#plt{info = Info, types = Types, + contracts = Contracts, + callbacks = Callbacks, exported_types = ExpTypes}, List) -> #plt{info = table_delete_list(Info, List), types = Types, contracts = table_delete_list(Contracts, List), + callbacks = table_delete_list(Callbacks, List), exported_types = ExpTypes}. -spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt(). @@ -126,16 +136,42 @@ delete_list(#plt{info = Info, types = Types, contracts = Contracts, insert_contract_list(#plt{contracts = Contracts} = PLT, List) -> PLT#plt{contracts = table_insert_list(Contracts, List)}. +-spec insert_callbacks(plt(), dialyzer_codeserver:codeserver()) -> plt(). + +insert_callbacks(#plt{callbacks = Callbacks} = Plt, Codeserver) -> + FunPreferNew = fun(_Key, _Val1, Val2) -> Val2 end, + FunDictMerger = + fun(_Key, Value, AccIn) -> dict:merge(FunPreferNew, Value, AccIn) end, + MergedCallbacks = dict:fold(FunDictMerger, dict:new(), + dialyzer_codeserver:get_callbacks(Codeserver)), + List = dict:to_list(MergedCallbacks), + Plt#plt{callbacks = table_insert_list(Callbacks, List)}. + -spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}. lookup_contract(#plt{contracts = Contracts}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Contracts, MFA). +-spec lookup_callbacks(plt(), module()) -> [{mfa(), + {{Filename::string(), + Line::pos_integer()}, + #contract{}}}]. + +lookup_callbacks(#plt{callbacks = Callbacks}, Mod) when is_atom(Mod) -> + FunModFilter = + fun({M, _F, _A}, _Val) -> M =:= Mod; + ( _Key, _Val) -> false + end, + ModCallbacks = dict:filter(FunModFilter, Callbacks), + dict:to_list(ModCallbacks). + -spec delete_contract_list(plt(), [mfa()]) -> plt(). -delete_contract_list(#plt{contracts = Contracts} = PLT, List) -> - PLT#plt{contracts = table_delete_list(Contracts, List)}. +delete_contract_list(#plt{contracts = Contracts, + callbacks = Callbacks} = PLT, List) -> + PLT#plt{contracts = table_delete_list(Contracts, List), + callbacks = table_delete_list(Callbacks, List)}. %% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt(). %% @@ -230,6 +266,7 @@ from_file(FileName, ReturnInfo) -> Plt = #plt{info = Rec#file_plt.info, types = Rec#file_plt.types, contracts = Rec#file_plt.contracts, + callbacks = Rec#file_plt.callbacks, exported_types = Rec#file_plt.exported_types}, case ReturnInfo of false -> Plt; @@ -284,26 +321,34 @@ get_record_from_file(FileName) -> -spec merge_plts([plt()]) -> plt(). merge_plts(List) -> - InfoList = [Info || #plt{info = Info} <- List], - TypesList = [Types || #plt{types = Types} <- List], - ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List], - ContractsList = [Contracts || #plt{contracts = Contracts} <- List], + {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} = + group_fields(List), #plt{info = table_merge(InfoList), types = table_merge(TypesList), exported_types = sets_merge(ExpTypesList), - contracts = table_merge(ContractsList)}. + contracts = table_merge(ContractsList), + callbacks = table_merge(CallbacksList) + }. -spec merge_disj_plts([plt()]) -> plt(). merge_disj_plts(List) -> + {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} = + group_fields(List), + #plt{info = table_disj_merge(InfoList), + types = table_disj_merge(TypesList), + exported_types = sets_disj_merge(ExpTypesList), + contracts = table_disj_merge(ContractsList), + callbacks = table_disj_merge(CallbacksList) + }. + +group_fields(List) -> InfoList = [Info || #plt{info = Info} <- List], TypesList = [Types || #plt{types = Types} <- List], ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List], ContractsList = [Contracts || #plt{contracts = Contracts} <- List], - #plt{info = table_disj_merge(InfoList), - types = table_disj_merge(TypesList), - exported_types = sets_disj_merge(ExpTypesList), - contracts = table_disj_merge(ContractsList)}. + CallbacksList = [Callbacks || #plt{callbacks = Callbacks} <- List], + {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList}. -spec merge_plts_or_report_conflicts([file:filename()], [plt()]) -> plt(). @@ -329,7 +374,7 @@ find_duplicates(List) -> to_file(FileName, #plt{info = Info, types = Types, contracts = Contracts, - exported_types = ExpTypes}, + callbacks = Callbacks, exported_types = ExpTypes}, ModDeps, {MD5, OldModDeps}) -> NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) @@ -340,6 +385,7 @@ to_file(FileName, file_md5_list = MD5, info = Info, contracts = Contracts, + callbacks = Callbacks, types = Types, exported_types = ExpTypes, mod_deps = NewModDeps, diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index dc5a3fed37..4d86bb34a7 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/7]). + get_warnings/6]). %% These are only intended as debug functions. -export([doit/1, @@ -106,21 +106,19 @@ 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(), boolean()) -> + pid()) -> {[dial_warning()], dialyzer_plt:plt(), doc_plt()}. -get_warnings(Callgraph, Plt, DocPlt, Codeserver, - NoWarnUnused, Parent, BehavioursChk) -> +get_warnings(Callgraph, Plt, DocPlt, Codeserver, NoWarnUnused, Parent) -> 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, BehavioursChk, CWarns). + get_warnings_from_modules(Mods, NewState, DocPlt, CWarns). -get_warnings_from_modules([M|Ms], State, DocPlt, - BehavioursChk, Acc) when is_atom(M) -> +get_warnings_from_modules([M|Ms], State, DocPlt, 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, @@ -135,19 +133,15 @@ get_warnings_from_modules([M|Ms], State, DocPlt, dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Records, NoWarnUnused), {NewAcc, Warnings2} = postprocess_dataflow_warns(RawWarnings2, State, Acc), Attrs = cerl:module_attrs(ModCode), - Warnings3 = if BehavioursChk -> - dialyzer_behaviours:check_callbacks(M, Attrs, - Plt, Codeserver); - true -> [] - end, + Warnings3 = dialyzer_behaviours:check_callbacks(M, Attrs, Plt, Codeserver), 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, BehavioursChk, + get_warnings_from_modules(Ms, State1, NewDocPlt, [Warnings1, Warnings2, Warnings3|NewAcc]); -get_warnings_from_modules([], #st{plt = Plt}, DocPlt, _, Acc) -> +get_warnings_from_modules([], #st{plt = Plt}, DocPlt, Acc) -> {lists:flatten(Acc), Plt, DocPlt}. postprocess_dataflow_warns(RawWarnings, State, WarnAcc) -> @@ -476,7 +470,7 @@ doit(Module) -> {ok, Code} = dialyzer_utils:get_core_from_abstract_code(AbstrCode), {ok, Records} = dialyzer_utils:get_record_and_type_info(AbstrCode), %% contract typing info in dictionary format - {ok, Contracts} = + {ok, Contracts, _Callbacks} = dialyzer_utils:get_spec_info(cerl:concrete(cerl:module_name(Code)), AbstrCode, Records), Sigs0 = get_top_level_signatures(Code, Records, Contracts), diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 92868b6878..4268814859 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -2046,8 +2046,7 @@ lookup_type(Key, Map) -> %% case cerl:is_literal(Key) of %% true -> t_from_term(cerl:concrete(Key)); %% false -> - Subst = t_subst(Key, Map), - t_sup(Subst, Subst). + t_subst(Key, Map). %% end. mk_var(Var) -> diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index 12f8dec67e..2a248fb028 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -311,11 +311,15 @@ merge_records(NewRecords, OldRecords) -> %% %% ============================================================================ +-type spec_dict() :: dict(). +-type callback_dict() :: dict(). + -spec get_spec_info(atom(), abstract_code(), dict()) -> - {'ok', dict()} | {'error', string()}. + {'ok', spec_dict(), callback_dict()} | {'error', string()}. get_spec_info(ModName, AbstractCode, RecordsDict) -> - get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile"). + get_spec_info(AbstractCode, dict:new(), dict:new(), + RecordsDict, ModName, "nofile"). %% TypeSpec is a list of conditional contracts for a function. %% Each contract is of the form {[Argument], Range, [Constraint]} where @@ -323,21 +327,34 @@ get_spec_info(ModName, AbstractCode, RecordsDict) -> %% - Constraint is of the form {subtype, T1, T2} where T1 and T2 %% are erl_types:erl_type() -get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], - SpecDict, RecordsDict, ModName, File) when is_list(TypeSpec) -> +get_spec_info([{attribute, Ln, Contract, {Id, TypeSpec}}|Left], + SpecDict, CallbackDict, RecordsDict, ModName, File) + when ((Contract =:= 'spec') or (Contract =:= 'callback')), + is_list(TypeSpec) -> MFA = case Id of {_, _, _} = T -> T; {F, A} -> {ModName, F, A} end, - try dict:find(MFA, SpecDict) of + ActiveDict = + case Contract of + spec -> SpecDict; + callback -> CallbackDict + end, + try dict:find(MFA, ActiveDict) of error -> - NewSpecDict = + NewActiveDict = dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, TypeSpec, - SpecDict, RecordsDict), - get_spec_info(Left, NewSpecDict, RecordsDict, ModName, File); + ActiveDict, RecordsDict), + {NewSpecDict, NewCallbackDict} = + case Contract of + spec -> {NewActiveDict, CallbackDict}; + callback -> {SpecDict, NewActiveDict} + end, + get_spec_info(Left, NewSpecDict, NewCallbackDict, + RecordsDict, ModName,File); {ok, {{OtherFile, L},_C}} -> {Mod, Fun, Arity} = MFA, - Msg = flat_format(" Contract for function ~w:~w/~w " + Msg = flat_format(" Contract/callback for function ~w:~w/~w " "already defined in ~s:~w\n", [Mod, Fun, Arity, OtherFile, L]), throw({error, Msg}) @@ -347,12 +364,14 @@ get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], [Ln, Error])} end; get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left], - SpecDict, RecordsDict, ModName, _File) -> - get_spec_info(Left, SpecDict, RecordsDict, ModName, IncludeFile); -get_spec_info([_Other|Left], SpecDict, RecordsDict, ModName, File) -> - get_spec_info(Left, SpecDict, RecordsDict, ModName, File); -get_spec_info([], SpecDict, _RecordsDict, _ModName, _File) -> - {ok, SpecDict}. + SpecDict, CallbackDict, RecordsDict, ModName, _File) -> + get_spec_info(Left, SpecDict, CallbackDict, + RecordsDict, ModName, IncludeFile); +get_spec_info([_Other|Left], SpecDict, CallbackDict, + RecordsDict, ModName, File) -> + get_spec_info(Left, SpecDict, CallbackDict, RecordsDict, ModName, File); +get_spec_info([], SpecDict, CallbackDict, _RecordsDict, _ModName, _File) -> + {ok, SpecDict, CallbackDict}. %% ============================================================================ %% |