File 0200-dialyzer-Fix-a-bug-regarding-contracts.patch of Package erlang

From 0d0799b4971e3353943614a311502cd4dd7b3ec9 Mon Sep 17 00:00:00 2001
From: Hans Bolinder <hasse@erlang.org>
Date: Tue, 5 Feb 2019 15:12:24 +0100
Subject: [PATCH] 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

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].
-- 
2.16.4

openSUSE Build Service is sponsored by