From 0d0799b4971e3353943614a311502cd4dd7b3ec9 Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Tue, 5 Feb 2019 15:12:24 +0100 Subject: dialyzer: Fix a bug regarding contracts See also https://bugs.erlang.org/browse/ERL-845. Fix a bug that caused Dialyzer to crash when analyzing a contract with a module name differing from the analyzed module's name. The bug was introduced in Erlang/OTP 18. --- lib/dialyzer/src/dialyzer_codeserver.erl | 8 +- lib/dialyzer/src/dialyzer_contracts.erl | 130 ++++++++++++--------- lib/dialyzer/src/dialyzer_utils.erl | 37 +++--- .../small_SUITE_data/results/spec_other_module | 2 + .../small_SUITE_data/src/spec_other_module.erl | 7 ++ 5 files changed, 107 insertions(+), 77 deletions(-) create mode 100644 lib/dialyzer/test/small_SUITE_data/results/spec_other_module create mode 100644 lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl (limited to 'lib/dialyzer') diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 5587cf2bdf..c4e3c322e5 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -347,13 +347,11 @@ get_file_contract(Key, ContDict) -> lookup_mfa_contract(MFA, #codeserver{contracts = ContDict}) -> ets_dict_find(MFA, ContDict). --spec lookup_meta_info(module() | mfa(), codeserver()) -> meta_info(). +-spec lookup_meta_info(module() | mfa(), codeserver()) -> + {'ok', meta_info()} | 'error'. lookup_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) -> - case ets_dict_find(MorMFA, FunMetaInfo) of - error -> []; - {ok, PropList} -> PropList - end. + ets_dict_find(MorMFA, FunMetaInfo). -spec get_contracts(codeserver()) -> dict:dict(mfa(), dialyzer_contracts:file_contract()). diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index af7f4385ad..9c36d745c3 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -25,7 +25,7 @@ %% get_contract_signature/1, is_overloaded/1, process_contract_remote_types/1, - store_tmp_contract/5]). + store_tmp_contract/6]). -export_type([file_contract/0, plt_contracts/0]). @@ -146,18 +146,18 @@ process_contract_remote_types(CodeServer) -> Mods = dialyzer_codeserver:all_temp_modules(CodeServer), RecordTable = dialyzer_codeserver:get_records_table(CodeServer), ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer), - ContractFun = - fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) -> - #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract, - {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) -> - CFun(ExpTypes, RecordTable, C1) - end, C0, CFuns), - Args = general_domain(NewCs), - Contract = #contract{contracts = NewCs, args = Args, forms = Forms}, - {{MFA, {File, Contract, Xtra}}, C2} - end, ModuleFun = fun(ModuleName) -> + ContractFun = + fun({MFA, {File, TmpContract, Xtra}}, C0) -> + #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract, + {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) -> + CFun(ExpTypes, RecordTable, C1) + end, C0, CFuns), + Args = general_domain(NewCs), + Contract = #contract{contracts = NewCs, args = Args, forms = Forms}, + {{MFA, {File, Contract, Xtra}}, C2} + end, Cache = erl_types:cache__new(), {ContractMap, CallbackMap} = dialyzer_codeserver:get_temp_contracts(ModuleName, CodeServer), @@ -474,26 +474,29 @@ insert_constraints([], Map) -> Map. -type spec_data() :: {TypeSpec :: [_], Xtra:: [_]}. --spec store_tmp_contract(mfa(), file_line(), spec_data(), contracts(), types()) -> - contracts(). +-spec store_tmp_contract(module(), mfa(), file_line(), spec_data(), + contracts(), types()) -> contracts(). -store_tmp_contract(MFA, FileLine, {TypeSpec, Xtra}, SpecMap, RecordsDict) -> +store_tmp_contract(Module, MFA, FileLine, {TypeSpec, Xtra}, SpecMap, + RecordsDict) -> %% io:format("contract from form: ~tp\n", [TypeSpec]), - TmpContract = contract_from_form(TypeSpec, MFA, RecordsDict, FileLine), + TmpContract = contract_from_form(TypeSpec, Module, MFA, RecordsDict, FileLine), %% io:format("contract: ~tp\n", [TmpContract]), maps:put(MFA, {FileLine, TmpContract, Xtra}, SpecMap). -contract_from_form(Forms, MFA, RecDict, FileLine) -> - {CFuns, Forms1} = contract_from_form(Forms, MFA, RecDict, FileLine, [], []), +contract_from_form(Forms, Module, MFA, RecDict, FileLine) -> + {CFuns, Forms1} = + contract_from_form(Forms, Module, MFA, RecDict, FileLine, [], []), #tmp_contract{contract_funs = CFuns, forms = Forms1}. -contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict, - FileLine, TypeAcc, FormAcc) -> +contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], Module, MFA, + RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = fun(ExpTypes, RecordTable, Cache) -> {NewType, NewCache} = try - from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache) + from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, + Cache) catch throw:{error, Msg} -> {File, Line} = FileLine, @@ -506,68 +509,74 @@ contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict, end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, []} | FormAcc], - contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc); + contract_from_form(Left, Module, MFA, RecDict, FileLine, NewTypeAcc, + NewFormAcc); contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], - MFA, RecDict, FileLine, TypeAcc, FormAcc) -> + Module, MFA, RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = fun(ExpTypes, RecordTable, Cache) -> {Constr1, VarTable, Cache1} = - process_constraints(Constr, MFA, RecDict, ExpTypes, RecordTable, - Cache), + process_constraints(Constr, Module, MFA, RecDict, ExpTypes, + RecordTable, Cache), {NewType, NewCache} = - from_form_with_check(Form, ExpTypes, MFA, RecordTable, + from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable, Cache1), NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), {{NewTypeNoVars, Constr1}, NewCache} end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, Constr} | FormAcc], - contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc); -contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) -> + contract_from_form(Left, Module, MFA, RecDict, FileLine, NewTypeAcc, + NewFormAcc); +contract_from_form([], _Mod, _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. -process_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> - {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, - RecordTable, Cache), +process_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, + Cache) -> + {Init0, NewCache} = initialize_constraints(Constrs, Module, MFA, RecDict, + ExpTypes, RecordTable, Cache), Init = remove_cycles(Init0), - constraints_fixpoint(Init, MFA, RecDict, ExpTypes, RecordTable, NewCache). + constraints_fixpoint(Init, Module, MFA, RecDict, ExpTypes, RecordTable, + NewCache). -initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> - initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, +initialize_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, + Cache) -> + initialize_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, Cache, []). -initialize_constraints([], _MFA, _RecDict, _ExpTypes, _RecordTable, +initialize_constraints([], _Module, _MFA, _RecDict, _ExpTypes, _RecordTable, Cache, Acc) -> {Acc, Cache}; -initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, RecordTable, - Cache, Acc) -> +initialize_constraints([Constr|Rest], Module, MFA, RecDict, ExpTypes, + RecordTable, Cache, Acc) -> case Constr of {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} -> VarTable = erl_types:var_table__new(), {T1, NewCache} = - final_form(Type1, ExpTypes, MFA, RecordTable, VarTable, Cache), + final_form(Type1, ExpTypes, Module, MFA, RecordTable, VarTable, Cache), Entry = {T1, Type2}, - initialize_constraints(Rest, MFA, RecDict, ExpTypes, RecordTable, - NewCache, [Entry|Acc]); + initialize_constraints(Rest, Module, MFA, RecDict, ExpTypes, + RecordTable, NewCache, [Entry|Acc]); {type, _, constraint, [{atom,_,Name}, List]} -> N = length(List), throw({error, io_lib:format("Unsupported type guard ~tw/~w\n", [Name, N])}) end. -constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> +constraints_fixpoint(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, + Cache) -> VarTable = erl_types:var_table__new(), {VarTab, NewCache} = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, + constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, VarTable, Cache), - constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes, + constraints_fixpoint(VarTab, Module, MFA, Constrs, RecDict, ExpTypes, RecordTable, NewCache). -constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes, +constraints_fixpoint(OldVarTab, Module, MFA, Constrs, RecDict, ExpTypes, RecordTable, Cache) -> {NewVarTab, NewCache} = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, + constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, OldVarTab, Cache), case NewVarTab of OldVarTab -> @@ -578,19 +587,23 @@ constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes, FinalConstrs = maps:fold(Fun, [], NewVarTab), {FinalConstrs, NewVarTab, NewCache}; _Other -> - constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes, + constraints_fixpoint(NewVarTab, Module, MFA, Constrs, RecDict, ExpTypes, RecordTable, NewCache) end. -final_form(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) -> - from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache). +final_form(Form, ExpTypes, Module, MFA, RecordTable, VarTable, Cache) -> + from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable, + Cache). -from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache) -> +from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, Cache) -> VarTable = erl_types:var_table__new(), - from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache). + from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable, + Cache). -from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) -> - Site = {spec, MFA}, +from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable, + Cache) -> + {_, F, A} = MFA, + Site = {spec, {Module, F, A}}, C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, RecordTable, VarTable, Cache), %% The check costs some time, and with the assumption that contracts @@ -598,22 +611,22 @@ from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) -> %% erl_types:t_from_form_check_remote(Form, ExpTypes, MFA, RecordTable), erl_types:t_from_form(Form, ExpTypes, Site, RecordTable, VarTable, C1). -constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, +constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, VarTab, Cache) -> {Subtypes, NewCache} = - constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, RecordTable, + constraints_to_subs(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable, VarTab, Cache, []), {insert_constraints(Subtypes), NewCache}. -constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _RecordTable, +constraints_to_subs([], _Module, _MFA, _RecDict, _ExpTypes, _RecordTable, _VarTab, Cache, Acc) -> {Acc, Cache}; -constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, RecordTable, - VarTab, Cache, Acc) -> +constraints_to_subs([{T1, Form2}|Rest], Module, MFA, RecDict, ExpTypes, + RecordTable, VarTab, Cache, Acc) -> {T2, NewCache} = - final_form(Form2, ExpTypes, MFA, RecordTable, VarTab, Cache), + final_form(Form2, ExpTypes, Module, MFA, RecordTable, VarTab, Cache), NewAcc = [{subtype, T1, T2}|Acc], - constraints_to_subs(Rest, MFA, RecDict, ExpTypes, RecordTable, + constraints_to_subs(Rest, Module, MFA, RecDict, ExpTypes, RecordTable, VarTab, NewCache, NewAcc). %% Replaces variables with '_' when necessary to break up cycles among @@ -898,6 +911,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) -> t_from_forms_without_remote([{FType, []}], MFA, RecDict) -> Site = {spec, MFA}, + %% FIXME Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict), {ok, erl_types:subst_all_vars_to_any(Type1)}; t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) -> diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index abd89034f3..310301ee0b 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -450,8 +450,9 @@ get_spec_info([{Contract, Ln, [{Id, TypeSpec}]}|Left], error -> SpecData = {TypeSpec, Xtra}, NewActiveMap = - dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, SpecData, - ActiveMap, RecordsMap), + dialyzer_contracts:store_tmp_contract(ModName, MFA, {File, Ln}, + SpecData, ActiveMap, + RecordsMap), {NewSpecMap, NewCallbackMap} = case Contract of spec -> {NewActiveMap, CallbackMap}; @@ -599,24 +600,32 @@ collect_attribute([], _Tag, _File) -> -spec is_suppressed_fun(mfa(), codeserver()) -> boolean(). is_suppressed_fun(MFA, CodeServer) -> - lookup_fun_property(MFA, nowarn_function, CodeServer). + lookup_fun_property(MFA, nowarn_function, CodeServer, false). -spec is_suppressed_tag(mfa() | module(), dial_warn_tag(), codeserver()) -> boolean(). is_suppressed_tag(MorMFA, Tag, Codeserver) -> - not lookup_fun_property(MorMFA, Tag, Codeserver). - -lookup_fun_property({M, _F, _A}=MFA, Property, CodeServer) -> - MFAPropList = dialyzer_codeserver:lookup_meta_info(MFA, CodeServer), - case proplists:get_value(Property, MFAPropList, no) of - mod -> false; % suppressed in function - func -> true; % requested in function - no -> lookup_fun_property(M, Property, CodeServer) + not lookup_fun_property(MorMFA, Tag, Codeserver, true). + +lookup_fun_property({M, _F, _A}=MFA, Property, CodeServer, NoInfoReturn) -> + case dialyzer_codeserver:lookup_meta_info(MFA, CodeServer) of + error -> + lookup_fun_property(M, Property, CodeServer, NoInfoReturn); + {ok, MFAPropList} -> + case proplists:get_value(Property, MFAPropList, no) of + mod -> false; % suppressed in function + func -> true; % requested in function + no -> lookup_fun_property(M, Property, CodeServer, NoInfoReturn) + end end; -lookup_fun_property(M, Property, CodeServer) when is_atom(M) -> - MPropList = dialyzer_codeserver:lookup_meta_info(M, CodeServer), - proplists:is_defined(Property, MPropList). +lookup_fun_property(M, Property, CodeServer, NoInfoReturn) when is_atom(M) -> + case dialyzer_codeserver:lookup_meta_info(M, CodeServer) of + error -> + NoInfoReturn; + {ok, MPropList} -> + proplists:is_defined(Property, MPropList) + end. %% ============================================================================ %% diff --git a/lib/dialyzer/test/small_SUITE_data/results/spec_other_module b/lib/dialyzer/test/small_SUITE_data/results/spec_other_module new file mode 100644 index 0000000000..ab2e35cf55 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/results/spec_other_module @@ -0,0 +1,2 @@ + +spec_other_module.erl:7: Contract for function that does not exist: lists:flatten/1 diff --git a/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl b/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl new file mode 100644 index 0000000000..b36742b1bd --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl @@ -0,0 +1,7 @@ +-module(spec_other_module). + +%% OTP-15562 and ERL-845. Example provided by Kostis. + +-type deep_list(A) :: [A | deep_list(A)]. + +-spec lists:flatten(deep_list(A)) -> [A]. -- cgit v1.2.3