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 | 127 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_behaviours.erl | 341 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl.erl | 16 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl_parse.erl | 9 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_codeserver.erl | 50 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_contracts.erl | 7 | ||||
-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_utils.erl | 49 |
12 files changed, 387 insertions, 358 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..62153fa176 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -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. diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 47ce9ba6eb..56eb46d78a 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -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,165 @@ 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, + SpecReturnType = dialyzer_contracts:get_contract_return(Contract), + SpecArgTypes = dialyzer_contracts:get_contract_args(Contract), + Acc11 = + case erl_types:t_is_subtype(SpecReturnType, CbReturnType) of + true -> Acc10; + false -> [{callback_spec_type_mismatch, + [File, Line, Behaviour, Function, Arity, + erl_types:t_to_string(SpecReturnType, 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 +280,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..e638e2fff2 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -754,15 +754,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..c34a9f0b7d 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -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..3469d70a4d 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -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(). 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_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}. %% ============================================================================ %% |