diff options
Diffstat (limited to 'lib/dialyzer')
50 files changed, 1186 insertions, 480 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..900ddf28c5 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,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..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..8b43740e34 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(). @@ -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_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}. %% ============================================================================ %% diff --git a/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options new file mode 100644 index 0000000000..50991c9bc5 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options @@ -0,0 +1 @@ +{dialyzer_options, []}. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs new file mode 100644 index 0000000000..33d135048e --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs @@ -0,0 +1,5 @@ + +my_callbacks_wrong.erl:26: The return type #state{parent::'undefined' | pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()} in the specification of callback_init/1 is not a subtype of {'ok',_}, which is the expected return type for the callback of my_behaviour behaviour +my_callbacks_wrong.erl:28: The inferred return type of callback_init/1 (#state{parent::'undefined' | pid(),status::'init',subscribe::[],counter::1}) has nothing in common with {'ok',_}, which is the expected return type for the callback of my_behaviour behaviour +my_callbacks_wrong.erl:30: The return type {'reply',#state{parent::'undefined' | pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()}} in the specification of callback_cast/3 is not a subtype of {'noreply',_}, which is the expected return type for the callback of my_behaviour behaviour +my_callbacks_wrong.erl:39: The specified type for the 2nd argument of callback_call/3 (atom()) is not a supertype of pid(), which is expected type for this argument in the callback of the my_behaviour behaviour diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_event_incorrect_return b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_event_incorrect_return new file mode 100644 index 0000000000..e646eea383 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_event_incorrect_return @@ -0,0 +1,2 @@ + +gen_event_incorrect_return.erl:16: The inferred return type of init/1 ('error') has nothing in common with {'error',_} | {'ok',_} | {'ok',_,'hibernate'}, which is the expected return type for the callback of gen_event behaviour diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args new file mode 100644 index 0000000000..3e98da785f --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args @@ -0,0 +1,8 @@ + +gen_server_incorrect_args.erl:3: Undefined callback function code_change/3 (behaviour 'gen_server') +gen_server_incorrect_args.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server') +gen_server_incorrect_args.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server') +gen_server_incorrect_args.erl:3: Undefined callback function init/1 (behaviour 'gen_server') +gen_server_incorrect_args.erl:3: Undefined callback function terminate/2 (behaviour 'gen_server') +gen_server_incorrect_args.erl:7: The inferred return type of handle_call/3 ({'no'} | {'ok'}) has nothing in common with {'noreply',_} | {'noreply',_,'hibernate' | 'infinity' | non_neg_integer()} | {'reply',_,_} | {'stop',_,_} | {'reply',_,_,'hibernate' | 'infinity' | non_neg_integer()} | {'stop',_,_,_}, which is the expected return type for the callback of gen_server behaviour +gen_server_incorrect_args.erl:7: The inferred type for the 2nd argument of handle_call/3 ('boo' | 'foo') is not a supertype of {pid(),_}, which is expected type for this argument in the callback of the gen_server behaviour diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks new file mode 100644 index 0000000000..5e0ed5fd27 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks @@ -0,0 +1,3 @@ + +gen_server_missing_callbacks.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server') +gen_server_missing_callbacks.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server') diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour new file mode 100644 index 0000000000..a38e662ccf --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour @@ -0,0 +1,9 @@ + +sample_callback_wrong.erl:15: The inferred return type of sample_callback_2/0 (42) has nothing in common with atom(), which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:16: The inferred return type of sample_callback_3/0 ('fair') has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:17: The inferred return type of sample_callback_4/1 ('fail') has nothing in common with 'ok', which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:19: The inferred return type of sample_callback_5/1 (string()) has nothing in common with 'fail' | 'ok', which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:19: The inferred type for the 1st argument of sample_callback_5/1 (atom()) is not a supertype of 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour +sample_callback_wrong.erl:21: The inferred return type of sample_callback_6/3 ({'okk',number()}) has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:21: The inferred type for the 3rd argument of sample_callback_6/3 (atom()) is not a supertype of string(), which is expected type for this argument in the callback of the sample_behaviour behaviour +sample_callback_wrong.erl:3: Undefined callback function sample_callback_1/0 (behaviour 'sample_behaviour') diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old new file mode 100644 index 0000000000..f0181bb59c --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour_old @@ -0,0 +1,4 @@ + +incorrect_args_callback.erl:12: The inferred type for the 2nd argument of bar/2 ('yes') is not a supertype of [any()], which is expected type for this argument in the callback of the correct_behaviour behaviour +incorrect_return_callback.erl:9: The inferred return type of foo/0 ('error') has nothing in common with 'no' | 'yes', which is the expected return type for the callback of correct_behaviour behaviour +missing_callback.erl:5: Undefined callback function foo/0 (behaviour 'correct_behaviour') diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/supervisor_incorrect_return b/lib/dialyzer/test/behaviour_SUITE_data/results/supervisor_incorrect_return new file mode 100644 index 0000000000..e89caf3cf7 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/supervisor_incorrect_return @@ -0,0 +1,2 @@ + +supervisor_incorrect_return.erl:14: The inferred return type of init/1 ({'ok',{{'one_against_one',0,1},[{_,_,_,_,_,_},...]}}) has nothing in common with 'ignore' | {'ok',{{'one_for_all',non_neg_integer(),non_neg_integer()} | {'one_for_one',non_neg_integer(),non_neg_integer()} | {'rest_for_one',non_neg_integer(),non_neg_integer()} | {'simple_one_for_one',non_neg_integer(),non_neg_integer()},[{_,{atom() | tuple(),atom(),'undefined' | [any()]},'permanent' | 'temporary' | 'transient','brutal_kill' | 'infinity' | non_neg_integer(),'supervisor' | 'worker','dynamic' | [atom() | tuple()]}]}}, which is the expected return type for the callback of supervisor behaviour diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/undefined_callbacks b/lib/dialyzer/test/behaviour_SUITE_data/results/undefined_callbacks new file mode 100644 index 0000000000..7147ffc750 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/undefined_callbacks @@ -0,0 +1,2 @@ + +undefined_beh_callback.erl:5: Callback info about the undefined_behaviour behaviour is not available diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec b/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec new file mode 100644 index 0000000000..5284e412f0 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec @@ -0,0 +1,6 @@ + +vars_in_beh_spec.erl:3: Undefined callback function handle_call/3 (behaviour 'gen_server') +vars_in_beh_spec.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server') +vars_in_beh_spec.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server') +vars_in_beh_spec.erl:3: Undefined callback function init/1 (behaviour 'gen_server') +vars_in_beh_spec.erl:3: Undefined callback function terminate/2 (behaviour 'gen_server') diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_behaviour.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_behaviour.erl new file mode 100644 index 0000000000..c4e5203448 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_behaviour.erl @@ -0,0 +1,11 @@ +-module(my_behaviour). + +-callback callback_init(Parent :: pid()) -> {'ok', State::term()}. + +-callback callback_cast(State::term(), From::pid(), Msg::term()) -> + {'noreply', NewState::term()}. + +-callback callback_call(State::term(), From::pid(), Msg::term()) -> + {'reply', NewState::term(), Reply::term()}. + +-callback callback_exit(State::term()) -> 'ok'. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl new file mode 100644 index 0000000000..041b4ac56c --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl @@ -0,0 +1,59 @@ +-module(my_callbacks_correct). + +-export([ + callback_init/1 + , callback_call/3 + , callback_cast/3 + , callback_exit/1 + ]). + +-record(state, { + parent :: pid(), + status = init :: 'init' | 'open' | 'closed', + subscribe = [] :: list({pid(), integer()}), + counter = 1 :: integer() + }). + +-type state() :: #state{}. + +-type cast_message() :: 'open' | 'closed'. + +-type call_message() :: 'subscribe' | 'unsubscribe'. +-type call_reply() :: 'accepted' | 'rejected'. + +-spec callback_init(Parent::pid()) -> {'ok', state()}. + +callback_init(Parent) -> + {ok, #state{parent = Parent}}. + +-spec callback_cast(state(), pid(), cast_message()) -> {'noreply', state()}. + +callback_cast(#state{parent = Pid} = State, Pid, Message) + when Message =:= 'open'; Message =:= 'close' -> + {noreply, State#state{status = Message}}; +callback_cast(State, _Pid, _Message) -> + {noreply, State}. + +-spec callback_call(state(), pid(), call_message()) -> + {'reply', state(), call_reply()}. + +callback_call(#state{status = open, subscribe = Subscribers} = State, + Pid, Message) + when Message =:= 'subscribe'; + Message =:= 'unsubscribe' -> + NewState = + case Message of + subscribe -> + N = State#state.counter, + State#state{subscribe = [{Pid, N}|Subscribers], counter = N+1}; + unsubscribe -> + State#state{subscribe = lists:keydelete(Pid, 1, Subscribers)} + end, + {reply, NewState, accepted}; +callback_call(State, _Pid, _Message) -> + {reply, State, rejected}. + +-spec callback_exit(state()) -> 'ok'. + +callback_exit(_State) -> + ok. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl new file mode 100644 index 0000000000..0459622dc1 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl @@ -0,0 +1,61 @@ +-module(my_callbacks_wrong). + +-export([ + callback_init/1 + , callback_call/3 + , callback_cast/3 + , callback_exit/1 + ]). + +-behaviour(my_behaviour). + +-record(state, { + parent :: pid(), + status = init :: 'init' | 'open' | 'closed', + subscribe = [] :: list({pid(), integer()}), + counter = 1 :: integer() + }). + +-type state() :: #state{}. + +-type cast_message() :: 'open' | 'closed'. + +-type call_message() :: 'subscribe' | 'unsubscribe'. +-type call_reply() :: 'accepted' | 'rejected'. + +-spec callback_init(Parent::pid()) -> state(). %% Wrong return spec + +callback_init(Parent) -> #state{parent = Parent}. %% Wrong return + +-spec callback_cast(state(), pid() | atom(), cast_message()) -> + {'noreply' | 'reply', state()}. %% More generic spec + +callback_cast(#state{parent = Pid} = State, Pid, Message) + when Message =:= 'open'; Message =:= 'close' -> + {noreply, State#state{status = Message}}; +callback_cast(State, _Pid, _Message) -> + {noreply, State}. + +-spec callback_call(state(), atom(), call_message()) -> %% Wrong arg spec + {'reply', state(), call_reply()}. + +callback_call(#state{status = open, subscribe = Subscribers} = State, + Pid, Message) + when Message =:= 'subscribe'; + Message =:= 'unsubscribe' -> + NewState = + case Message of + subscribe -> + N = State#state.counter, + State#state{subscribe = [{Pid, N}|Subscribers], counter = N+1}; + unsubscribe -> + State#state{subscribe = lists:keydelete(Pid, 1, Subscribers)} + end, + {reply, NewState, accepted}; +callback_call(State, _Pid, _Message) -> + {reply, State, rejected}. + +-spec callback_exit(state()) -> ok. + +callback_exit(_State) -> + ok. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/gen_event_incorrect_return.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_event_incorrect_return.erl new file mode 100644 index 0000000000..f5ccf7f8c4 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_event_incorrect_return.erl @@ -0,0 +1,33 @@ +-module(gen_event_incorrect_return). + +-behaviour(gen_event). + +-export([start_link/0, add_handler/0]). + +-export([init/1, handle_event/2, handle_call/2, + handle_info/2, terminate/2, code_change/3]). + +start_link() -> + gen_event:start_link({local, myserver}). + +add_handler() -> + gen_event:add_handler(myserver, ?MODULE, []). + +init([]) -> + error. + +handle_event(_Event, State) -> + {ok, State}. + +handle_call(_Request, State) -> + Reply = ok, + {ok, Reply, State}. + +handle_info(_Info, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_incorrect_args.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_incorrect_args.erl new file mode 100644 index 0000000000..df04dff80d --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_incorrect_args.erl @@ -0,0 +1,11 @@ +-module(gen_server_incorrect_args). + +-behaviour(gen_server). + +-export([handle_call/3]). + +handle_call(_Request, From, _State) -> + case From of + 'boo' -> {'ok'}; + 'foo' -> {'no'} + end. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_missing_callbacks.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_missing_callbacks.erl new file mode 100644 index 0000000000..760466fdac --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/gen_server_missing_callbacks.erl @@ -0,0 +1,23 @@ +-module(gen_server_missing_callbacks). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, handle_call/3, terminate/2, code_change/3]). + +start_link() -> + gen_server:start_link({local, myserver}, ?MODULE, [], []). + +init([]) -> + ignore. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_behaviour.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_behaviour.erl new file mode 100644 index 0000000000..116980986b --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_behaviour.erl @@ -0,0 +1,13 @@ +-module(sample_behaviour). + +-type custom() :: 1..42. + +-callback sample_callback_1() -> term(). +-callback sample_callback_2() -> atom(). +-callback sample_callback_3() -> {'ok', custom()} | 'fail'. + +-callback sample_callback_4(term()) -> 'ok'. +-callback sample_callback_5(custom()) -> 'ok' | 'fail'. + +-callback sample_callback_6(custom(), custom(), string()) -> + {'ok', custom()} | 'fail'. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct.erl new file mode 100644 index 0000000000..ab0378e6f0 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct.erl @@ -0,0 +1,32 @@ +-module(sample_callback_correct). + +-behaviour(sample_behaviour). + +-export([ + sample_callback_1/0, + sample_callback_2/0, + sample_callback_3/0, + sample_callback_4/1, + sample_callback_5/1, + sample_callback_6/3 + ]). + +sample_callback_1() -> 42. % This is a valid return. +sample_callback_2() -> foo. % This is a valid return. +sample_callback_3() -> {ok, 17}. % This is a valid return. +sample_callback_4(Input) -> + put(mine, Input+1), % This is valid handling of the input + ok. % This is a valid return. +sample_callback_5(Input) -> + case Input - 1 < 22 of % This is valid handling of the input + true -> ok; % This is a valid return. + false -> fail % This is a valid return. + end. +sample_callback_6(OldNr, NewNr, Reason) -> + Diff = NewNr - OldNr, % This is valid handling of the input + Msg = string:join(["Reason: ", Reason], ","), % This is valid handling of the input + case Diff > 0 of + true -> put(mine, {NewNr, Msg}), + {ok, NewNr}; % This is a valid return. + false -> fail % This is a valid return. + end. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl new file mode 100644 index 0000000000..c218174e58 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl @@ -0,0 +1,38 @@ +-module(sample_callback_correct_2). + +-behaviour(sample_behaviour). + +-export([ + sample_callback_1/0, + sample_callback_2/0, + sample_callback_3/0, + sample_callback_4/1, + sample_callback_5/1, + sample_callback_6/3, + common_infrastructure/1 + ]). + +sample_callback_1() -> 42. % This is a valid return. +sample_callback_2() -> halt(). % Crashes are also allowed. +sample_callback_3() -> {ok, 17}. % This is a valid return. +sample_callback_4(Input) -> + case Input of + 1 -> common_infrastructure(Input); % This is 'correct' input for + _ -> ok % common_infrastructure. + end. +sample_callback_5(Input) -> + case get(Input) of % This is valid handling of a more generic input + true -> ok; % This is a valid return. + false -> fail % This is a valid return. + end. +sample_callback_6(OldNr, NewNr, Reason) -> + Diff = NewNr - OldNr, % This is valid handling of the input + Msg = string:join(["Reason: ", Reason], ","), % This is valid handling of the input + case Diff > 0 of + true -> put(mine, {NewNr, Msg}), + {ok, NewNr}; % This is a valid return. + false -> fail % This is a valid return. + end. + +common_infrastructure( 1) -> 'ok'; +common_infrastructure(42) -> 'fail'. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl new file mode 100644 index 0000000000..02a063fab7 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl @@ -0,0 +1,25 @@ +-module(sample_callback_wrong). + +-behaviour(sample_behaviour). + +-export([ +% sample_callback_1/0, + sample_callback_2/0, + sample_callback_3/0, + sample_callback_4/1, + sample_callback_5/1, + sample_callback_6/3 + ]). + +% sample_callback_1() -> 41. % We can't really break this contract so: missing! +sample_callback_2() -> 42. % This is not an atom(). +sample_callback_3() -> fair. % This is probably a typo. +sample_callback_4(_) -> % We cannot break the input. + fail. % We can definitely return a wrong value however. :) +sample_callback_5(Input) -> % Input is treated as an atom, result is a list. + atom_to_list(Input). % Both violate the contract. +sample_callback_6(OldNr, NewNr, Reason) -> + Diff = NewNr - OldNr, % This is valid handling of the input + %% Reason should have been treated as a string. + Msg = string:join(["Reason: ", atom_to_list(Reason)], ","), + {okk, NewNr}. %% This, too, is a typo. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_behaviour.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_behaviour.erl new file mode 100644 index 0000000000..90ce590997 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_behaviour.erl @@ -0,0 +1,6 @@ +%%% This is a behaviour with info about its calllbacks. + +-module(correct_behaviour). + +-callback foo() -> yes | no. +-callback bar({atom(),_},[_]) -> term(). diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_callback.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_callback.erl new file mode 100644 index 0000000000..8f254520ab --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/correct_callback.erl @@ -0,0 +1,15 @@ +%%% This is a correct callback module for the correct_behaviour. + +-module(correct_callback). + +-behaviour(correct_behaviour). + +-export([foo/0, bar/2]). + +foo() -> + yes. + +bar({'query', 'boo'}, _Any) -> + no; +bar({'reply', [_R]}, [1,2,3]) -> + yes. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_args_callback.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_args_callback.erl new file mode 100644 index 0000000000..68fc60d418 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_args_callback.erl @@ -0,0 +1,13 @@ +%%% This is a correct callback module for the correct_behaviour. + +-module(incorrect_args_callback). + +-behaviour(correct_behaviour). + +-export([foo/0, bar/2]). + +foo() -> + yes. + +bar({'reply', _Any}, yes) -> %% Should be a tuple and a list. + yes. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_return_callback.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_return_callback.erl new file mode 100644 index 0000000000..9ff920cdd0 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/incorrect_return_callback.erl @@ -0,0 +1,15 @@ +%%% This is a correct callback module for the correct_behaviour. + +-module(incorrect_return_callback). + +-behaviour(correct_behaviour). + +-export([foo/0, bar/2]). + +foo() -> + error. %% Should be 'yes' or 'no'. + +bar({'query', 'boo'}, _Any) -> + no; +bar({'reply', [_R]}, [1,2,3]) -> + yes. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/missing_callback.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/missing_callback.erl new file mode 100644 index 0000000000..e6c5306839 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour_old/missing_callback.erl @@ -0,0 +1,10 @@ +%%% This is a correct callback module for the correct_behaviour. + +-module(missing_callback). + +-behaviour(correct_behaviour). + +-export([bar/2]). + +bar({'reply', _Any}, []) -> + yes. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/supervisor_incorrect_return.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/supervisor_incorrect_return.erl new file mode 100644 index 0000000000..616a9073ae --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/supervisor_incorrect_return.erl @@ -0,0 +1,17 @@ +-module(supervisor_incorrect_return). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + AChild = {'AName',{'AModule',start_link,[]}, + permanent,2000,worker,['AModule']}, + {ok,{{one_against_one,0,1}, [AChild]}}. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_beh_callback.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_beh_callback.erl new file mode 100644 index 0000000000..8223225b4b --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_beh_callback.erl @@ -0,0 +1,13 @@ +%%% This is a correct callback module for the correct_behaviour. + +-module(undefined_beh_callback). + +-behaviour(undefined_behaviour). + +-export([foo/0, bar/2]). + +foo() -> + yes. + +bar({'reply', _Any}, yes) -> + yes. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_behaviour.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_behaviour.erl new file mode 100644 index 0000000000..fb3d4c5e03 --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/undefined_callbacks/undefined_behaviour.erl @@ -0,0 +1,10 @@ +%%% This is a behaviour with undefined info about its calllbacks. + +-module(undefined_behaviour). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{foo, 0}, {bar, 2}]; +behaviour_info(_Other) -> + undefined. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/vars_in_beh_spec.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/vars_in_beh_spec.erl new file mode 100644 index 0000000000..dc75b30d0e --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/vars_in_beh_spec.erl @@ -0,0 +1,10 @@ +-module(vars_in_beh_spec). + +-behaviour(gen_server). + +-export([code_change/3]). + +-spec code_change(_, State, _) -> {ok, State}. + +code_change(_, State, _) -> + {ok, State}. diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/inets b/lib/dialyzer/test/r9c_SUITE_data/results/inets index 0177dcc88c..24cb39e52b 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/inets +++ b/lib/dialyzer/test/r9c_SUITE_data/results/inets @@ -3,21 +3,15 @@ ftp.erl:1243: The pattern {'ok', {N, Bytes}} can never match the type 'eof' | {' ftp.erl:640: The pattern {'closed', _Why} can never match the type 'perm_fname_not_allowed' | 'perm_neg_compl' | 'perm_no_space' | 'pos_compl' | 'pos_interm' | 'pos_interm_acct' | 'trans_neg_compl' | 'trans_no_space' | {'error' | 'perm_fname_not_allowed' | 'perm_neg_compl' | 'perm_no_space' | 'pos_compl' | 'pos_interm' | 'pos_interm_acct' | 'pos_prel' | 'trans_neg_compl' | 'trans_no_space',atom() | [any()] | {'invalid_server_response',[any(),...]}} http.erl:117: The pattern {'error', Reason} can never match the type #req_headers{connection::[45 | 97 | 101 | 105 | 107 | 108 | 112 | 118,...],content_length::[48,...],other::[{_,_}]} http.erl:138: Function close_session/2 will never be called -http_lib.erl:286: The call http_lib:close('ip_comm' | {'ssl',_},port() | {'sslsocket',_,_}) will never return since it differs in the 1st argument from the success typing arguments: ('http' | 'https',port() | {'sslsocket',_,pid() | {_,{'config',_,_,_,_,{_,_,_,_}}} | {'sslsocket',_,pid() | {'sslsocket',_,pid() | {_,_,_}}}}) -http_lib.erl:415: The pattern 61 can never match the type 'http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | #http_request{method::'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),path::'*' | binary() | string() | {'abs_path',binary() | string()} | {'scheme',binary() | string(),binary() | string()} | {'absoluteURI','http' | 'https',binary() | string(),'undefined' | non_neg_integer(),binary() | string()},version::{non_neg_integer(),non_neg_integer()}} | #http_response{version::{non_neg_integer(),non_neg_integer()},status::integer(),phrase::binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()} -http_lib.erl:417: The pattern 59 can never match the type 'http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | #http_request{method::'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),path::'*' | binary() | string() | {'abs_path',binary() | string()} | {'scheme',binary() | string(),binary() | string()} | {'absoluteURI','http' | 'https',binary() | string(),'undefined' | non_neg_integer(),binary() | string()},version::{non_neg_integer(),non_neg_integer()}} | #http_response{version::{non_neg_integer(),non_neg_integer()},status::integer(),phrase::binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()} -http_lib.erl:420: The pattern 13 can never match the type 'http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | #http_request{method::'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),path::'*' | binary() | string() | {'abs_path',binary() | string()} | {'scheme',binary() | string(),binary() | string()} | {'absoluteURI','http' | 'https',binary() | string(),'undefined' | non_neg_integer(),binary() | string()},version::{non_neg_integer(),non_neg_integer()}} | #http_response{version::{non_neg_integer(),non_neg_integer()},status::integer(),phrase::binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()} -http_lib.erl:424: The variable _ can never match since previous clauses completely covered the type 'http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | #http_request{method::'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),path::'*' | binary() | string() | {'abs_path',binary() | string()} | {'scheme',binary() | string(),binary() | string()} | {'absoluteURI','http' | 'https',binary() | string(),'undefined' | non_neg_integer(),binary() | string()},version::{non_neg_integer(),non_neg_integer()}} | #http_response{version::{non_neg_integer(),non_neg_integer()},status::integer(),phrase::binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()} -http_lib.erl:428: Function read_chunk_ext_val/6 will never be called -http_lib.erl:444: The pattern 10 can never match the type 'http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | #http_request{method::'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),path::'*' | binary() | string() | {'abs_path',binary() | string()} | {'scheme',binary() | string(),binary() | string()} | {'absoluteURI','http' | 'https',binary() | string(),'undefined' | non_neg_integer(),binary() | string()},version::{non_neg_integer(),non_neg_integer()}} | #http_response{version::{non_neg_integer(),non_neg_integer()},status::integer(),phrase::binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()} -http_lib.erl:552: Call to missing or unexported function ssl:accept/2 +http_lib.erl:286: The call http_lib:close('ip_comm' | {'ssl',_},any()) will never return since it differs in the 1st argument from the success typing arguments: ('http' | 'https',any()) +http_lib.erl:424: The variable _ can never match since previous clauses completely covered the type any() +http_lib.erl:438: The variable _ can never match since previous clauses completely covered the type any() http_lib.erl:99: Function getHeaderValue/2 will never be called httpc_handler.erl:660: Function exit_session_ok/2 has no local return httpc_manager.erl:145: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} httpc_manager.erl:160: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} httpc_manager.erl:478: The pattern {'error', Reason} can never match the type 'ok' | {number(),#session{clientclose::boolean(),pipeline::[],quelength::1}} httpc_manager.erl:490: The pattern {'error', Reason} can never match the type 'ok' | {number(),#session{clientclose::boolean(),pipeline::[],quelength::1}} -httpd.erl:583: The pattern <{'error', Reason}, _Fd, SoFar> can never match the type <[any()],pid(),[[any(),...]]> httpd_acceptor.erl:105: The pattern {'error', Reason} can never match the type {'ok',pid()} httpd_acceptor.erl:110: Function handle_connection_err/4 will never be called httpd_acceptor.erl:168: Function report_error/2 will never be called @@ -26,14 +20,10 @@ httpd_manager.erl:885: The pattern {'EXIT', Reason} can never match since previo httpd_manager.erl:919: Function auth_status/1 will never be called httpd_manager.erl:926: Function sec_status/1 will never be called httpd_manager.erl:933: Function acceptor_status/1 will never be called -httpd_request_handler.erl:374: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 66 | 98 | 100 | 103 | 105 | 111 | 116 | 121,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_},socket::port() | {'sslsocket',_,_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) -httpd_request_handler.erl:378: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_},socket::port() | {'sslsocket',_,_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) -httpd_request_handler.erl:401: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_},socket::port() | {'sslsocket',_,_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) -httpd_request_handler.erl:489: The variable Other can never match since previous clauses completely covered the type {'error',_} | {'ok','http_eoh' | binary() | maybe_improper_list(any(),binary() | []) | {'http_error',binary() | string()} | {'http_request','DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'TRACE' | binary() | string(),'*' | binary() | string() | {'abs_path',binary() | [any()]} | {'scheme',binary() | [any()],binary() | [any()]} | {'absoluteURI','http' | 'https',binary() | [any()],'undefined' | non_neg_integer(),binary() | [any()]},{non_neg_integer(),non_neg_integer()}} | {'http_response',{non_neg_integer(),non_neg_integer()},integer(),binary() | string()} | {'http_header',integer(),atom() | binary() | string(),_,binary() | string()}} -httpd_request_handler.erl:644: The call lists:reverse(Fields0::{'error',_} | {'ok',_}) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) -httpd_request_handler.erl:645: Function will never be called -httpd_socket.erl:129: Call to missing or unexported function ssl:accept/2 -httpd_socket.erl:49: The pattern {'ok', _} can never match the type {'error',_} +httpd_request_handler.erl:374: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 66 | 98 | 100 | 103 | 105 | 111 | 116 | 121,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) +httpd_request_handler.erl:378: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) +httpd_request_handler.erl:401: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) +httpd_request_handler.erl:649: Guard test [{_,_}] =:= Trailers::nonempty_string() can never succeed httpd_sup.erl:63: The variable Else can never match since previous clauses completely covered the type {'error',_} | {'ok',[any()],_,_} httpd_sup.erl:88: The pattern {'error', Reason} can never match the type {'ok',_,_} httpd_sup.erl:92: The variable Else can never match since previous clauses completely covered the type {'ok',_,_} @@ -48,10 +38,10 @@ mod_dir.erl:72: The pattern {'error', Reason} can never match the type {'ok',[[[ mod_get.erl:135: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_head.erl:80: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_htaccess.erl:460: The pattern {'error', BadData} can never match the type {'ok',_} -mod_include.erl:193: The pattern {_, Name, {[], []}} can never match the type {[any()],[any()],string()} -mod_include.erl:195: The pattern {_, Name, {PathInfo, []}} can never match the type {[any()],[any()],string()} -mod_include.erl:197: The pattern {_, Name, {PathInfo, QueryString}} can never match the type {[any()],[any()],string()} -mod_include.erl:201: The variable Gurka can never match since previous clauses completely covered the type {[any()],[any()],string()} +mod_include.erl:193: The pattern {_, Name, {[], []}} can never match the type {[any()],[any()],maybe_improper_list()} +mod_include.erl:195: The pattern {_, Name, {PathInfo, []}} can never match the type {[any()],[any()],maybe_improper_list()} +mod_include.erl:197: The pattern {_, Name, {PathInfo, QueryString}} can never match the type {[any()],[any()],maybe_improper_list()} +mod_include.erl:201: The variable Gurka can never match since previous clauses completely covered the type {[any()],[any()],maybe_improper_list()} mod_include.erl:692: The pattern <{'read', Reason}, Info, Path> can never match the type <{'open',atom()},#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_include.erl:706: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_include.erl:716: Function read_error/3 will never be called diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/mnesia b/lib/dialyzer/test/r9c_SUITE_data/results/mnesia index 2be71ac7d7..b397d37523 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/mnesia +++ b/lib/dialyzer/test/r9c_SUITE_data/results/mnesia @@ -2,6 +2,7 @@ mnesia.erl:1319: Guard test size(Spec::[{_,_,_},...]) can never succeed mnesia.erl:1498: The call mnesia:bad_info_reply(Tab::atom(),Item::'type') will never return since it differs in the 2nd argument from the success typing arguments: (atom(),'memory' | 'size') mnesia.erl:331: Function mod2abs/1 has no local return +mnesia_backup.erl:49: Callback info about the mnesia_backup behaviour is not available mnesia_bup.erl:111: The created fun has no local return mnesia_bup.erl:574: Function fallback_receiver/2 has no local return mnesia_bup.erl:967: Function uninstall_fallback_master/2 has no local return @@ -12,9 +13,12 @@ mnesia_controller.erl:1679: The pattern {'stop', Reason, Reply, State2} can neve mnesia_controller.erl:1685: The pattern {'noreply', State2, _Timeout} can never match the type {'reply',_,_} mnesia_event.erl:77: The pattern 'remove_handler' can never match the type {'ok',_} mnesia_event.erl:79: The pattern {'swap_handler', Args1, State1, Mod2, Args2} can never match the type {'ok',_} +mnesia_frag.erl:26: Callback info about the mnesia_access behaviour is not available mnesia_frag.erl:294: The call mnesia_frag:remote_collect(Ref::reference(),{'error',_},[],OldSelectFun::fun(() -> [any()])) will never return since it differs in the 2nd argument from the success typing arguments: (reference(),'ok',[any()],fun(() -> [any()])) mnesia_frag.erl:304: The call mnesia_frag:remote_collect(Ref::reference(),{'error',{'node_not_running',_}},[],OldSelectFun::fun(() -> [any()])) will never return since it differs in the 2nd argument from the success typing arguments: (reference(),'ok',[any()],fun(() -> [any()])) mnesia_frag.erl:312: The call mnesia_frag:remote_collect(Ref::reference(),LocalRes::{'error',_},[],OldSelectFun::fun(() -> [any()])) will never return since it differs in the 2nd argument from the success typing arguments: (reference(),'ok',[any()],fun(() -> [any()])) +mnesia_frag_hash.erl:24: Callback info about the mnesia_frag_hash behaviour is not available +mnesia_frag_old_hash.erl:23: Callback info about the mnesia_frag_hash behaviour is not available mnesia_index.erl:52: The call mnesia_lib:other_val(Var::{_,'commit_work' | 'index' | 'setorbag' | 'storage_type' | {'index',_}},_ReASoN_::any()) will never return since it differs in the 1st argument from the success typing arguments: ({_,'active_replicas' | 'where_to_read' | 'where_to_write'},any()) mnesia_lib.erl:1028: The pattern {'EXIT', Reason} can never match the type [any()] | {'error',_} mnesia_lib.erl:957: The pattern {'ok', {0, _}} can never match the type 'eof' | {'error',atom()} | {'ok',binary() | string()} diff --git a/lib/dialyzer/test/small_SUITE_data/src/maybe_improper.erl b/lib/dialyzer/test/small_SUITE_data/src/maybe_improper.erl new file mode 100644 index 0000000000..1743d81493 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/maybe_improper.erl @@ -0,0 +1,7 @@ +-module(maybe_improper). + +-export([s/1]). + +-spec s(maybe_improper_list(X,Y)) -> {[X], maybe_improper_list(X,Y)}. +s(A) -> + lists:split(2,A). diff --git a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options new file mode 100644 index 0000000000..f7197ac30f --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options @@ -0,0 +1 @@ +{dialyzer_options, [{warnings, [underspecs]}]}. diff --git a/lib/dialyzer/test/underspecs_SUITE_data/results/remote b/lib/dialyzer/test/underspecs_SUITE_data/results/remote new file mode 100644 index 0000000000..1e0cda3bde --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/results/remote @@ -0,0 +1,9 @@ + +remotes1.erl:17: The specification for remotes1:foo5/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:20: Type specification remotes1:foo6('ok' | 'ko') -> 'ok' is a supertype of the success typing: remotes1:foo6('ok') -> 'ok' +remotes1.erl:25: The specification for remotes1:foo7/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:28: Type specification remotes1:foo8(local_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo8('ok') -> 'ok' +remotes1.erl:33: The specification for remotes1:foo9/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:36: Type specification remotes1:foo10(local_and_known_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo10('ok') -> 'ok' +remotes1.erl:49: Type specification remotes1:foo13('ok') -> local_and_unknown_remote_type_42() is a supertype of the success typing: remotes1:foo13('ok') -> 'ok' +remotes1.erl:52: Type specification remotes1:foo14(local_and_unknown_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo14('ok') -> 'ok' diff --git a/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl new file mode 100644 index 0000000000..b722495095 --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl @@ -0,0 +1,61 @@ +-module(remotes1). + +-compile(export_all). + +-spec foo1(some_unknown_remote:type42()) -> ok. +foo1(ok) -> ok. + +-spec foo2(ok) -> some_unknown_remote:type42(). +foo2(ok) -> ok. + +-spec foo3(some_known_remote:type42()) -> ok. +foo3(ok) -> ok. + +-spec foo4(ok) -> some_known_remote:type42(). +foo4(ok) -> ok. + +-spec foo5(ok|ko) -> ok|ko. +foo5(ok) -> ok. + +-spec foo6(ok|ko) -> ok. +foo6(ok) -> ok. + +-type local_type_42() :: ok | ko. + +-spec foo7(ok) -> local_type_42(). +foo7(ok) -> ok. + +-spec foo8(local_type_42()) -> ok. +foo8(ok) -> ok. + +-type local_and_known_remote_type_42() :: some_known_remote:type42() | ok | ko. + +-spec foo9(ok) -> local_and_known_remote_type_42(). +foo9(ok) -> ok. + +-spec foo10(local_and_known_remote_type_42()) -> ok. +foo10(ok) -> ok. + +-type local_and_ok_known_remote_type_42() :: some_known_remote:type42() | ok. + +-spec foo11(ok) -> local_and_ok_known_remote_type_42(). +foo11(ok) -> ok. + +-spec foo12(local_and_ok_known_remote_type_42()) -> ok. +foo12(ok) -> ok. + +-type local_and_unknown_remote_type_42() :: some_unknown_remote:type42() | ok | ko. + +-spec foo13(ok) -> local_and_unknown_remote_type_42(). +foo13(ok) -> ok. + +-spec foo14(local_and_unknown_remote_type_42()) -> ok. +foo14(ok) -> ok. + +-type local_and_ok_unknown_remote_type_42() :: some_unknown_remote:type42() | ok. + +-spec foo15(ok) -> local_and_ok_unknown_remote_type_42(). +foo15(ok) -> ok. + +-spec foo16(local_and_ok_unknown_remote_type_42()) -> ok. +foo16(ok) -> ok. diff --git a/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl new file mode 100644 index 0000000000..437f1e7826 --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl @@ -0,0 +1,5 @@ +-module(some_known_remote). + +-export_type([type42/0]). + +-type type42() :: ok | ko. |