diff options
Diffstat (limited to 'lib/dialyzer')
48 files changed, 868 insertions, 365 deletions
diff --git a/lib/dialyzer/doc/src/book.xml b/lib/dialyzer/doc/src/book.xml index 0b4e1cb617..ec06427671 100644 --- a/lib/dialyzer/doc/src/book.xml +++ b/lib/dialyzer/doc/src/book.xml @@ -4,7 +4,7 @@ <book xmlns:xi="http://www.w3.org/2001/XInclude"> <header titlestyle="normal"> <copyright> - <year>2006</year><year>2009</year> + <year>2006</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index 17291b24f7..f100865b56 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -31,6 +31,94 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 2.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix false warning about closure application</p> + <p> + Whenever a variable that could hold one of two or more + possible closures was used in a particular application, + the application was assumed to fail if ONE of the + closures would fail in this application. This has been + corrected to infer failing application if ALL possible + closures would fail in the particular application.</p> + <p> + Change category of 'might also return' warnings</p> + <p> + Dialyzer emits warnings like the following "The + specification for _ states that the function might also + return _ but the inferred return is _", which are + actually underspecifications and not wrong type + specifications. This patch makes sure that they are filed + under the appropriate category.</p> + <p> + Own Id: OTP-9707</p> + </item> + <item> + <p>Wrap up behaviours patch for Dialyzer</p> <list> + <item><p>Enable warnings by default, add two options for + suppressing them</p></item> <item><p>Fix warning + formatting and update testsuites.</p></item> + <item><p>Detection of callback-spec + discrepancies</p></item> <item><p>Allow none() as return + value in callbacks</p></item> <item><p>Behaviour callback + discrepancy detection for Dialyzer</p></item> + <item><p>Add lookup function for callbacks</p></item> + <item><p>Store callbacks in codeserver and PLT</p></item> + <item><p>Collect callback definitions during + compilation</p></item> <item><p>Update inets + results</p></item> </list> + <p> + Own Id: OTP-9731</p> + </item> + <item> + <p> + <list> <item><p>No warnings for underspecs with remote + types</p></item> <item><p> Fix crash in Typer</p></item> + <item><p>Fix Dialyzer's warning for its own + code</p></item> <item><p>Fix Dialyzer's warnings in + HiPE</p></item> <item><p>Add file/line info in a + particular Dialyzer crash</p></item> <item><p>Update + inets test results</p></item> </list></p> + <p> + Own Id: OTP-9758</p> + </item> + <item> + <p> + <list> <item><p>Correct callback spec in application + module</p></item> <item><p>Refine warning about callback + specs with extra ranges</p></item> <item><p>Cleanup + autoimport compiler directives</p></item> <item><p>Fix + Dialyzer's warnings in typer</p></item> <item><p>Fix + Dialyzer's warning for its own code</p></item> + <item><p>Fix bug in Dialyzer's behaviours + analysis</p></item> <item><p>Fix crash in + Dialyzer</p></item> <item><p>Variable substitution was + not generalizing any unknown variables.</p></item> + </list></p> + <p> + Own Id: OTP-9776</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Optimize the joining of maps in + <c>dialyzer_dataflow</c>. </p> + <p> + Own Id: OTP-9761</p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 2.4.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/doc/src/part.xml b/lib/dialyzer/doc/src/part.xml index 4410840660..564ef3a52f 100644 --- a/lib/dialyzer/doc/src/part.xml +++ b/lib/dialyzer/doc/src/part.xml @@ -4,7 +4,7 @@ <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2006</year><year>2009</year> + <year>2006</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> diff --git a/lib/dialyzer/doc/src/part_notes.xml b/lib/dialyzer/doc/src/part_notes.xml index cb048d55dd..992ee73daa 100644 --- a/lib/dialyzer/doc/src/part_notes.xml +++ b/lib/dialyzer/doc/src/part_notes.xml @@ -4,7 +4,7 @@ <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2006</year><year>2009</year> + <year>2006</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> diff --git a/lib/dialyzer/doc/src/ref_man.xml b/lib/dialyzer/doc/src/ref_man.xml index ca5410f6b8..7e5367b7c5 100644 --- a/lib/dialyzer/doc/src/ref_man.xml +++ b/lib/dialyzer/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2006</year><year>2009</year> + <year>2006</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 62153fa176..5be870d78f 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -2,7 +2,7 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -170,33 +170,32 @@ analysis_start(Parent, Analysis) -> rcv_and_send_ext_types(Parent), NonExports = sets:subtract(sets:from_list(AllNodes), Exports), NonExportsList = sets:to_list(NonExports), - Plt3 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), - Plt4 = dialyzer_plt:delete_contract_list(Plt3, NonExportsList), + Plt2 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), send_codeserver_plt(Parent, CServer, State3#analysis_state.plt), - send_analysis_done(Parent, Plt4, State3#analysis_state.doc_plt). + send_analysis_done(Parent, Plt2, State3#analysis_state.doc_plt). analyze_callgraph(Callgraph, State) -> 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 -> - Callgraph1 = dialyzer_callgraph:finalize(Callgraph), - NewPlt = dialyzer_succ_typings:analyze_callgraph(Callgraph1, Plt, - Codeserver, Parent), - dialyzer_callgraph:delete(Callgraph1), - State#analysis_state{plt = NewPlt}; - succ_typings -> - NoWarn = State#analysis_state.no_warn_unused, - 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), - dialyzer_callgraph:delete(Callgraph1), - send_warnings(State#analysis_state.parent, Warnings), - State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} - end. + DocPlt = State#analysis_state.doc_plt, + Plt = dialyzer_plt:insert_callbacks(State#analysis_state.plt, Codeserver), + Callgraph1 = dialyzer_callgraph:finalize(Callgraph), + {NewPlt, NewDocPlt} = + case State#analysis_state.analysis_type of + plt_build -> + {dialyzer_succ_typings:analyze_callgraph(Callgraph1, Plt, + Codeserver, Parent), + DocPlt}; + succ_typings -> + NoWarn = State#analysis_state.no_warn_unused, + {Warnings, NewPlt0, NewDocPlt0} = + dialyzer_succ_typings:get_warnings(Callgraph1, Plt, DocPlt, + Codeserver, NoWarn, Parent), + send_warnings(State#analysis_state.parent, Warnings), + {NewPlt0, NewDocPlt0} + end, + dialyzer_callgraph:delete(Callgraph1), + State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt}. %%-------------------------------------------------------------------- %% Build the callgraph and fill the codeserver. @@ -282,10 +281,10 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, if ExtCalls1 =:= [] -> {[], []}; true -> ModuleSet = sets:from_list(Modules), - lists:partition(fun({_From, {M, _F, _A}}) -> - sets:is_element(M, ModuleSet) orelse - dialyzer_plt:contains_module(InitPlt, M) - end, ExtCalls1) + PltModuleSet = dialyzer_plt:all_modules(InitPlt), + AllModules = sets:union(ModuleSet, PltModuleSet), + Pred = fun({_From, {M, _F, _A}}) -> sets:is_element(M, AllModules) end, + lists:partition(Pred, ExtCalls1) end, NonLocalCalls = dialyzer_callgraph:non_local_calls(Callgraph1), BadCalls2 = [Call || Call = {_From, To} <- NonLocalCalls, @@ -359,8 +358,19 @@ store_core(Mod, Core, NoWarn, Callgraph, CServer) -> store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer3, NoWarn). abs_get_nowarn(Abs, M) -> - [{M, F, A} - || {attribute, _, compile, {nowarn_unused_function, {F, A}}} <- Abs]. + Opts = lists:flatten([C || {attribute, _, compile, C} <- Abs]), + Warn = erl_lint:bool_option(warn_unused_function, nowarn_unused_function, + true, Opts), + case Warn of + false -> + [{M, F, A} || {function, _, F, A, _} <- Abs]; % all functions + true -> + OnLoad = + lists:flatten([{M, F, A} || {attribute, _, on_load, {F, A}} <- Abs]), + OnLoad ++ [{M, F, A} || + {nowarn_unused_function, FAs} <- Opts, + {F, A} <- lists:flatten([FAs])] + end. get_exported_types_from_core(Core) -> Attrs = cerl:module_attrs(Core), diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 56eb46d78a..fdaa8b663c 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010. All Rights Reserved. +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -72,9 +72,11 @@ check_callbacks(Module, Attrs, Plt, Codeserver) -> %%-------------------------------------------------------------------- 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'], + BehaviourListsAndLine = + [{cerl:concrete(L2), hd(cerl:get_ann(L2))} || + {L1, L2} <- Attrs, cerl:is_literal(L1), + cerl:is_literal(L2), cerl:concrete(L1) =:= 'behaviour' orelse + cerl:concrete(L1) =:= 'behavior'], Behaviours = lists:append([Behs || {Behs,_} <- BehaviourListsAndLine]), BehLines = [{B,L} || {L1,L} <- BehaviourListsAndLine, B <- L1], {Behaviours, BehLines}. @@ -144,15 +146,20 @@ check_all_callbacks(Module, Behaviour, [Cb|Rest], 'error' -> Acc1; {ok, {{File, Line}, Contract}} -> Acc10 = Acc1, - SpecReturnType = dialyzer_contracts:get_contract_return(Contract), - SpecArgTypes = dialyzer_contracts:get_contract_args(Contract), + 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 -> [{callback_spec_type_mismatch, - [File, Line, Behaviour, Function, Arity, - erl_types:t_to_string(SpecReturnType, Records), - erl_types:t_to_string(CbReturnType, Records)]}|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( @@ -234,12 +241,12 @@ translatable_behaviours(Tree) -> get_behaviour_apis(Behaviours) -> get_behaviour_apis(Behaviours, []). --spec translate_behaviour_api_call(dialyzer_races:mfa_or_funlbl(), +-spec translate_behaviour_api_call(dialyzer_callgraph:mfa_or_funlbl(), [erl_types:erl_type()], [dialyzer_races:core_vars()], module(), behaviour_api_dict()) -> - {dialyzer_races:mfa_or_funlbl(), + {dialyzer_callgraph:mfa_or_funlbl(), [erl_types:erl_type()], [dialyzer_races:core_vars()]} | 'plain_call'. diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index d3de5aaf45..4767c02f77 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -59,7 +59,7 @@ put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2, get_behaviour_api_calls/1]). --export_type([callgraph/0]). +-export_type([callgraph/0, mfa_or_funlbl/0]). -include("dialyzer.hrl"). @@ -177,14 +177,14 @@ is_escaping(Label, #callgraph{esc = Esc}) when is_integer(Label) -> add_edges([], CG) -> CG; -add_edges(Edges, #callgraph{digraph = Callgraph} = CG) -> - CG#callgraph{digraph = digraph_add_edges(Edges, Callgraph)}. +add_edges(Edges, #callgraph{digraph = Digraph} = CG) -> + CG#callgraph{digraph = digraph_add_edges(Edges, Digraph)}. -spec add_edges([callgraph_edge()], [mfa_or_funlbl()], callgraph()) -> callgraph(). add_edges(Edges, MFAs, #callgraph{digraph = DG} = CG) -> - DG1 = digraph_confirm_vertices(MFAs, DG), - add_edges(Edges, CG#callgraph{digraph = DG1}). + DG = digraph_confirm_vertices(MFAs, DG), + add_edges(Edges, CG). -spec take_scc(callgraph()) -> 'none' | {'ok', scc(), callgraph()}. @@ -196,8 +196,8 @@ take_scc(#callgraph{postorder = []}) -> -spec remove_external(callgraph()) -> {callgraph(), [tuple()]}. remove_external(#callgraph{digraph = DG} = CG) -> - {NewDG, External} = digraph_remove_external(DG), - {CG#callgraph{digraph = NewDG}, External}. + {DG, External} = digraph_remove_external(DG), + {CG, External}. -spec non_local_calls(callgraph()) -> mfa_calls(). @@ -241,30 +241,30 @@ modules(#callgraph{digraph = DG}) -> -spec module_postorder(callgraph()) -> [[module()]]. module_postorder(#callgraph{digraph = DG}) -> + {MDG, _Nodes} = get_module_digraph_and_nodes(DG), + MDG1 = digraph_utils:condensation(MDG), + PostOrder = digraph_utils:postorder(MDG1), + PostOrder1 = sort_sccs_internally(PostOrder, MDG), + digraph:delete(MDG1), + digraph_delete(MDG), + PostOrder1. + +get_module_digraph_and_nodes(DG) -> Edges = digraph_edges(DG), Nodes = ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), MDG = digraph:new(), - MDG1 = digraph_confirm_vertices(Nodes, MDG), - MDG2 = create_module_digraph(Edges, MDG1), - MDG3 = digraph_utils:condensation(MDG2), - PostOrder = digraph_utils:postorder(MDG3), - PostOrder1 = sort_sccs_internally(PostOrder, MDG2), - digraph:delete(MDG2), - digraph_delete(MDG3), - PostOrder1. + MDG = digraph_confirm_vertices(Nodes, MDG), + MDG = create_module_digraph(Edges, MDG), + {MDG, Nodes}. %% The module deps of a module are modules that depend on the module -spec module_deps(callgraph()) -> dict(). module_deps(#callgraph{digraph = DG}) -> - Edges = digraph_edges(DG), - Nodes = ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), - MDG = digraph:new(), - MDG1 = digraph_confirm_vertices(Nodes, MDG), - MDG2 = create_module_digraph(Edges, MDG1), - Deps = [{N, ordsets:from_list(digraph:in_neighbours(MDG2, N))} + {MDG, Nodes} = get_module_digraph_and_nodes(DG), + Deps = [{N, ordsets:from_list(digraph:in_neighbours(MDG, N))} || N <- Nodes], - digraph_delete(MDG2), + digraph_delete(MDG), dict:from_list(Deps). -spec strip_module_deps(dict(), set()) -> dict(). diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index e638e2fff2..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]) diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index c34a9f0b7d..f1e87affbd 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -292,15 +292,11 @@ table__loop(Cached, Map) -> {NewCached, Ans} = case Cached of {M, Tree} -> - [Val] = [VarFun || {Var, _Fun} = VarFun <- cerl:module_defs(Tree), - cerl:fname_id(Var) =:= F, - cerl:fname_arity(Var) =:= A], + Val = find_fun(F, A, Tree), {Cached, Val}; _ -> Tree = fetch_and_expand(M, Map), - [Val] = [VarFun || {Var, _Fun} = VarFun <- cerl:module_defs(Tree), - cerl:fname_id(Var) =:= F, - cerl:fname_arity(Var) =:= A], + Val = find_fun(F, A, Tree), {{M, Tree}, Val} end, Pid ! {self(), MFA, Ans}, @@ -329,3 +325,12 @@ fetch_and_expand(Mod, Map) -> Msg = "found no module named '" ++ S ++ "' in the analyzed files", exit({error, Msg}) end. + +find_fun(F, A, Tree) -> + Pred = + fun({Var, _Fun}) -> + (cerl:fname_id(Var) =/= F) orelse + (cerl:fname_arity(Var) =/= A) + end, + [Val|_] = lists:dropwhile(Pred, cerl:module_defs(Tree)), + Val. diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 3469d70a4d..2b78b736ab 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -256,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}} @@ -352,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) @@ -385,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}, @@ -444,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 -> @@ -507,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..aba13278ff 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -67,7 +67,6 @@ %%-define(DEBUG, true). %%-define(DEBUG_PP, true). -%%-define(DEBUG_TIME, true). %%-define(DOT, true). -ifdef(DEBUG). @@ -77,9 +76,6 @@ -define(debug(S_, L_), ok). -endif. -%%-define(debug1(S_, L_), io:format(S_, L_)). -%%-define(debug1(S_, L_), ok). - %%-------------------------------------------------------------------- -define(no_arg, no_arg). @@ -101,6 +97,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{}. @@ -270,6 +272,7 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> debug_pp(Tree, false), Module = cerl:atom_val(cerl:module_name(Tree)), RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), + RaceCode = dialyzer_callgraph:get_race_code(Callgraph), BehaviourTranslations = case RaceDetection of true -> dialyzer_behaviours:translatable_behaviours(Tree); @@ -280,9 +283,6 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> TopFun, Plt, Module, Records, BehaviourTranslations), State1 = state__race_analysis(not GetWarnings, State), State2 = analyze_loop(State1), - RaceCode = dialyzer_callgraph:get_race_code(Callgraph), - Callgraph1 = State2#state.callgraph, - RaceCode1 = dialyzer_callgraph:get_race_code(Callgraph1), case GetWarnings of true -> State3 = state__set_warning_mode(State2), @@ -307,6 +307,8 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> St#state{callgraph = Callgraph3} end; false -> + Callgraph1 = State2#state.callgraph, + RaceCode1 = dialyzer_callgraph:get_race_code(Callgraph1), state__restore_race_code( dict:merge(fun (_K, V1, _V2) -> V1 end, RaceCode, RaceCode1), State2) @@ -314,27 +316,26 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> case state__get_work(State) of - none -> state__clean_not_called(State); - {Fun, NewState} -> - ArgTypes = state__get_args(Fun, NewState), - case any_none(ArgTypes) of + none -> State; + {Fun, NewState1} -> + {ArgTypes, IsCalled} = state__get_args_and_status(Fun, NewState1), + case not IsCalled of true -> - ?debug("Not handling1 ~w: ~s\n", + ?debug("Not handling (not called) ~w: ~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), - analyze_loop(NewState); + analyze_loop(NewState1); false -> - case state__fun_env(Fun, NewState) of + case state__fun_env(Fun, NewState1) of none -> - ?debug("Not handling2 ~w: ~s\n", + ?debug("Not handling (no env) ~w: ~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), - analyze_loop(NewState); + analyze_loop(NewState1); Map -> ?debug("Handling fun ~p: ~s\n", [state__lookup_name(get_label(Fun), State), - t_to_string(state__fun_type(Fun, NewState))]), - NewState1 = state__mark_fun_as_handled(NewState, Fun), + t_to_string(state__fun_type(Fun, NewState1))]), Vars = cerl:fun_vars(Fun), Map1 = enter_type_lists(Vars, ArgTypes, Map), Body = cerl:fun_body(Fun), @@ -1058,12 +1059,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 +1642,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 +1664,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 +2311,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 +2349,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 +2378,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 +2500,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 +2602,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 +2612,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 +2662,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 +2685,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 +2707,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 +2738,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 +2795,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", @@ -2836,28 +2887,22 @@ state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques), - Work = init_work([get_label(Tree)]), - Env = dict:store(top, map__new(), dict:new()), + ExportedFuns = + [Fun || Fun <- Funs--[top], dialyzer_callgraph:is_escaping(Fun, Callgraph)], + Work = init_work(ExportedFuns), + Env = lists:foldl(fun(Fun, Env) -> dict:store(Fun, map__new(), Env) end, + dict:new(), Funs), #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, module = Module, behaviour_api_dict = BehaviourTranslations}. -state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> - Fun = get_label(Fun0), - case dict:find(Fun, FunTab) of - {ok, {not_handled, Entry}} -> - State#state{fun_tab = dict:store(Fun, Entry, FunTab)}; - {ok, {_, _}} -> - State - end. - state__warning_mode(#state{warning_mode = WM}) -> WM. state__set_warning_mode(#state{tree_map = TreeMap, fun_tab = FunTab, races = Races} = State) -> - ?debug("Starting warning pass\n", []), + ?debug("==========\nStarting warning pass\n==========\n", []), Funs = dict:fetch_keys(TreeMap), State#state{work = init_work([top|Funs--[top]]), fun_tab = FunTab, warning_mode = true, @@ -2929,7 +2974,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, {NotCalled, Ret} = case dict:fetch(get_label(Fun), FunTab) of {not_handled, {_Args0, Ret0}} -> {true, Ret0}; - {Args0, Ret0} -> {any_none(Args0), Ret0} + {_Args0, Ret0} -> {false, Ret0} end, case NotCalled of true -> @@ -3023,11 +3068,11 @@ state__lookup_record(Tag, Arity, #state{records = Records}) -> error end. -state__get_args(Tree, #state{fun_tab = FunTab}) -> +state__get_args_and_status(Tree, #state{fun_tab = FunTab}) -> Fun = get_label(Tree), case dict:find(Fun, FunTab) of - {ok, {not_handled, {ArgTypes, _}}} -> ArgTypes; - {ok, {ArgTypes, _}} -> ArgTypes + {ok, {not_handled, {ArgTypes, _}}} -> {ArgTypes, false}; + {ok, {ArgTypes, _}} -> {ArgTypes, true} end. build_tree_map(Tree) -> @@ -3043,7 +3088,7 @@ build_tree_map(Tree) -> cerl_trees:fold(Fun, dict:new(), Tree). init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> - NewDict = dict:store(top, {not_handled, {[], t_none()}}, Dict), + NewDict = dict:store(top, {[], t_none()}, Dict), init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)), @@ -3059,9 +3104,9 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> false -> {Args, t_unit()} end end; - false -> {lists:duplicate(Arity, t_none()), t_unit()} + false -> {not_handled, {lists:duplicate(Arity, t_none()), t_unit()}} end, - NewDict = dict:store(Fun, {not_handled, FunEntry}, Dict), + NewDict = dict:store(Fun, FunEntry, Dict), init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) -> Dict. @@ -3085,7 +3130,8 @@ state__clean_not_called(#state{fun_tab = FunTab} = State) -> end, FunTab), State#state{fun_tab = NewFunTab}. -state__all_fun_types(#state{fun_tab = FunTab}) -> +state__all_fun_types(State) -> + #state{fun_tab = FunTab} = state__clean_not_called(State), Tab1 = dict:erase(top, FunTab), dict:map(fun(_Fun, {Args, Ret}) -> t_fun(Args, Ret)end, Tab1). diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl index ccd80a4835..bac659548f 100644 --- a/lib/dialyzer/src/dialyzer_gui.erl +++ b/lib/dialyzer/src/dialyzer_gui.erl @@ -28,6 +28,23 @@ %%%----------------------------------------------------------------------- -module(dialyzer_gui). +-compile([{nowarn_deprecated_function,{gs,button,2}}, + {nowarn_deprecated_function,{gs,config,2}}, + {nowarn_deprecated_function,{gs,destroy,1}}, + {nowarn_deprecated_function,{gs,editor,2}}, + {nowarn_deprecated_function,{gs,entry,2}}, + {nowarn_deprecated_function,{gs,frame,2}}, + {nowarn_deprecated_function,{gs,label,2}}, + {nowarn_deprecated_function,{gs,listbox,2}}, + {nowarn_deprecated_function,{gs,menu,2}}, + {nowarn_deprecated_function,{gs,menubar,2}}, + {nowarn_deprecated_function,{gs,menubutton,2}}, + {nowarn_deprecated_function,{gs,menuitem,2}}, + {nowarn_deprecated_function,{gs,radiobutton,2}}, + {nowarn_deprecated_function,{gs,read,2}}, + {nowarn_deprecated_function,{gs,start,0}}, + {nowarn_deprecated_function,{gs,stop,0}}, + {nowarn_deprecated_function,{gs,window,2}}]). -export([start/1]). @@ -494,6 +511,16 @@ gui_loop(#gui_state{add_all = AddAll, add_file = AddFile, add_rec = AddRec, [ExtCalls]), free_editor(State, "Analysis done", Msg), gui_loop(State); + {BackendPid, ext_types, ExtTypes} -> + Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end, + ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"), + Msg = io_lib:format("The following remote types are being used " + "but information about them is not available.\n" + "The analysis might get more precise by including " + "the modules containing these types and making sure " + "that they are exported:\n~s\n", [ExtTypeString]), + free_editor(State, "Analysis done", Msg), + gui_loop(State); {BackendPid, log, LogMsg} -> update_editor(Log, LogMsg), gui_loop(State); diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index e711c15ea7..9ff32bd8b1 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -503,6 +503,16 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, [ExtCalls]), free_editor(State,"Analysis Done", Msg), gui_loop(State); + {BackendPid, ext_types, ExtTypes} -> + Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end, + ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"), + Msg = io_lib:format("The following remote types are being used " + "but information about them is not available.\n" + "The analysis might get more precise by including " + "the modules containing these types and making sure " + "that they are exported:\n~s\n", [ExtTypeString]), + free_editor(State, "Analysis done", Msg), + gui_loop(State); {BackendPid, log, LogMsg} -> update_editor(Log, LogMsg), gui_loop(State); diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index 206c43e4e2..74892d1668 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -31,8 +31,7 @@ -export([check_plt/3, compute_md5_from_files/1, contains_mfa/2, - contains_module/2, - delete_contract_list/2, + all_modules/1, delete_list/2, delete_module/2, included_files/1, @@ -153,10 +152,8 @@ 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{}}}]. +-spec lookup_callbacks(plt(), module()) -> + [{mfa(), {{Filename::string(), Line::pos_integer()}, #contract{}}}]. lookup_callbacks(#plt{callbacks = Callbacks}, Mod) when is_atom(Mod) -> FunModFilter = @@ -166,18 +163,6 @@ lookup_callbacks(#plt{callbacks = Callbacks}, Mod) when is_atom(Mod) -> ModCallbacks = dict:filter(FunModFilter, Callbacks), dict:to_list(ModCallbacks). --spec delete_contract_list(plt(), [mfa()]) -> plt(). - -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(). -%% -%% insert(#plt{info = Info} = PLT, Id, Types) -> -%% PLT#plt{info = table_insert(Info, Id, Types)}. - -type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}. -spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt(). @@ -220,10 +205,10 @@ get_exported_types(#plt{exported_types = ExpTypes}) -> lookup_module(#plt{info = Info}, M) when is_atom(M) -> table_lookup_module(Info, M). --spec contains_module(plt(), atom()) -> boolean(). +-spec all_modules(plt()) -> set(). -contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> - table_contains_module(Info, M) orelse table_contains_module(Cs, M). +all_modules(#plt{info = Info, contracts = Cs}) -> + sets:union(table_all_modules(Info), table_all_modules(Cs)). -spec contains_mfa(plt(), mfa()) -> boolean(). @@ -623,10 +608,12 @@ table_lookup_module(Plt, Mod) -> false -> {value, List} end. -table_contains_module(Plt, Mod) -> - dict:fold(fun({M, _F, _A}, _Val, _Acc) when M =:= Mod -> true; - (_, _, Acc) -> Acc - end, false, Plt). +table_all_modules(Plt) -> + Fold = + fun({M, _F, _A}, _Val, Acc) -> sets:add_element(M, Acc); + (_, _, Acc) -> Acc + end, + dict:fold(Fold, sets:new(), Plt). table_merge([H|T]) -> table_merge(T, H). diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index ee9d5e88a3..b9594f5301 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -39,7 +39,7 @@ let_tag_new/2, new/0, put_curr_fun/3, put_fun_args/2, put_race_analysis/2, put_race_list/3]). --export_type([races/0, mfa_or_funlbl/0, core_vars/0]). +-export_type([races/0, core_vars/0]). -include("dialyzer.hrl"). @@ -66,8 +66,6 @@ %%% %%% =========================================================================== --type mfa_or_funlbl() :: label() | mfa(). - -type label_type() :: label() | [label()] | {label()} | ?no_label. -type args() :: [label_type() | [string()]]. -type core_vars() :: cerl:cerl() | ?no_arg | ?bypassed. @@ -94,7 +92,7 @@ guard :: cerl:cerl()}). -record(end_case, {clauses :: [#end_clause{}]}). -record(curr_fun, {status :: 'in' | 'out', - mfa :: mfa_or_funlbl(), + mfa :: dialyzer_callgraph:mfa_or_funlbl(), label :: label(), def_vars :: [core_vars()], arg_types :: [erl_types:erl_type()], @@ -107,8 +105,8 @@ state :: _, %% XXX: recursive file_line :: file_line(), var_map :: dict()}). --record(fun_call, {caller :: mfa_or_funlbl(), - callee :: mfa_or_funlbl(), +-record(fun_call, {caller :: dialyzer_callgraph:mfa_or_funlbl(), + callee :: dialyzer_callgraph:mfa_or_funlbl(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()]}). -record(let_tag, {var :: var_to_map1(), @@ -130,10 +128,10 @@ vars :: [core_vars()], file_line :: file_line(), index :: non_neg_integer(), - fun_mfa :: mfa_or_funlbl(), + fun_mfa :: dialyzer_callgraph:mfa_or_funlbl(), fun_label :: label()}). --record(races, {curr_fun :: mfa_or_funlbl(), +-record(races, {curr_fun :: dialyzer_callgraph:mfa_or_funlbl(), curr_fun_label :: label(), curr_fun_args = 'empty' :: core_args(), new_table = 'no_t' :: table(), @@ -158,7 +156,8 @@ %%% %%% =========================================================================== --spec store_race_call(mfa_or_funlbl(), [erl_types:erl_type()], [core_vars()], +-spec store_race_call(dialyzer_callgraph:mfa_or_funlbl(), + [erl_types:erl_type()], [core_vars()], file_line(), dialyzer_dataflow:state()) -> dialyzer_dataflow:state(). @@ -2405,7 +2404,7 @@ end_case_new(Clauses) -> end_clause_new(Arg, Pats, Guard) -> #end_clause{arg = Arg, pats = Pats, guard = Guard}. --spec get_curr_fun(races()) -> mfa_or_funlbl(). +-spec get_curr_fun(races()) -> dialyzer_callgraph:mfa_or_funlbl(). get_curr_fun(#races{curr_fun = CurrFun}) -> CurrFun. @@ -2444,7 +2443,7 @@ let_tag_new(Var, Arg) -> new() -> #races{}. --spec put_curr_fun(mfa_or_funlbl(), label(), races()) -> +-spec put_curr_fun(dialyzer_callgraph:mfa_or_funlbl(), label(), races()) -> races(). put_curr_fun(CurrFun, CurrFunLabel, Races) -> diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index 4d86bb34a7..de4c8a32a3 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -204,7 +204,7 @@ refine_one_module(M, State) -> #st{callgraph = Callgraph, codeserver = CodeServer, plt = PLT} = State, ModCode = dialyzer_codeserver:lookup_mod_code(M, CodeServer), AllFuns = collect_fun_info([ModCode]), - FunTypes = get_fun_types_from_plt(AllFuns, State), + FunTypes = get_fun_types_from_plt(AllFuns, Callgraph, PLT), Records = dialyzer_codeserver:lookup_mod_records(M, CodeServer), {NewFunTypes, RaceCode, PublicTables, NamedTables} = dialyzer_dataflow:get_fun_types(ModCode, PLT, Callgraph, Records), @@ -321,7 +321,9 @@ find_succ_typings(#st{callgraph = Callgraph, parent = Parent} = State, end end. -analyze_scc(SCC, #st{codeserver = Codeserver} = State) -> +analyze_scc(SCC, #st{codeserver = Codeserver, + callgraph = Callgraph, + plt = Plt} = State) -> SCC_Info = [{MFA, dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), dialyzer_codeserver:lookup_mod_records(M, Codeserver)} @@ -330,23 +332,19 @@ analyze_scc(SCC, #st{codeserver = Codeserver} = State) -> || {_, _, _} = MFA <- SCC], Contracts2 = [{MFA, Contract} || {MFA, {ok, Contract}} <- Contracts1], Contracts3 = orddict:from_list(Contracts2), + NextLabel = dialyzer_codeserver:get_next_core_label(Codeserver), {SuccTypes, PltContracts, NotFixpoint} = - find_succ_types_for_scc(SCC_Info, Contracts3, State), + find_succ_types_for_scc(SCC_Info, Contracts3, NextLabel, Callgraph, Plt), State1 = insert_into_plt(SuccTypes, State), ContrPlt = dialyzer_plt:insert_contract_list(State1#st.plt, PltContracts), {State1#st{plt = ContrPlt}, NotFixpoint}. -find_succ_types_for_scc(SCC_Info, Contracts, - #st{codeserver = Codeserver, - callgraph = Callgraph, plt = Plt} = State) -> +find_succ_types_for_scc(SCC_Info, Contracts, NextLabel, Callgraph, Plt) -> %% Assume that the PLT contains the current propagated types AllFuns = collect_fun_info([Fun || {_MFA, {_Var, Fun}, _Rec} <- SCC_Info]), - PropTypes = get_fun_types_from_plt(AllFuns, State), - MFAs = [MFA || {MFA, {_Var, _Fun}, _Rec} <- SCC_Info], - NextLabel = dialyzer_codeserver:get_next_core_label(Codeserver), - Plt1 = dialyzer_plt:delete_contract_list(Plt, MFAs), + PropTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt), FunTypes = dialyzer_typesig:analyze_scc(SCC_Info, NextLabel, - Callgraph, Plt1, PropTypes), + Callgraph, Plt, PropTypes), AllFunSet = sets:from_list([X || {X, _} <- AllFuns]), FilteredFunTypes = dict:filter(fun(X, _) -> sets:is_element(X, AllFunSet) @@ -372,13 +370,13 @@ find_succ_types_for_scc(SCC_Info, Contracts, ordsets:from_list([Fun || {Fun, _Arity} <- AllFuns])} end. -get_fun_types_from_plt(FunList, State) -> - get_fun_types_from_plt(FunList, State, dict:new()). +get_fun_types_from_plt(FunList, Callgraph, Plt) -> + get_fun_types_from_plt(FunList, Callgraph, Plt, dict:new()). -get_fun_types_from_plt([{FunLabel, Arity}|Left], State, Map) -> - Type = lookup_fun_type(FunLabel, Arity, State), - get_fun_types_from_plt(Left, State, dict:store(FunLabel, Type, Map)); -get_fun_types_from_plt([], _State, Map) -> +get_fun_types_from_plt([{FunLabel, Arity}|Left], Callgraph, Plt, Map) -> + Type = lookup_fun_type(FunLabel, Arity, Callgraph, Plt), + get_fun_types_from_plt(Left, Callgraph, Plt, dict:store(FunLabel, Type, Map)); +get_fun_types_from_plt([], _Callgraph, _Plt, Map) -> Map. collect_fun_info(Trees) -> @@ -396,7 +394,7 @@ collect_fun_info([Tree|Trees], List) -> collect_fun_info([], List) -> List. -lookup_fun_type(Label, Arity, #st{callgraph = Callgraph, plt = Plt}) -> +lookup_fun_type(Label, Arity, Callgraph, Plt) -> ID = lookup_name(Label, Callgraph), case dialyzer_plt:lookup(Plt, ID) of none -> erl_types:t_fun(Arity, erl_types:t_any()); diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 92868b6878..04ff8e4941 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -91,22 +91,24 @@ -type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, dict()}]. -type typesig_funmap() :: [{type_var(), type_var()}]. %% Orddict --record(state, {callgraph :: dialyzer_callgraph:callgraph(), - cs = [] :: [constr()], - cmap = dict:new() :: dict(), - fun_map = [] :: typesig_funmap(), - fun_arities = dict:new() :: dict(), - in_match = false :: boolean(), - in_guard = false :: boolean(), - module :: module(), - name_map = dict:new() :: dict(), - next_label :: label(), - non_self_recs = [] :: [label()], - plt :: dialyzer_plt:plt(), - prop_types = dict:new() :: dict(), - records = dict:new() :: dict(), - opaques = [] :: [erl_types:erl_type()], - scc = [] :: [type_var()]}). +-record(state, {callgraph :: dialyzer_callgraph:callgraph(), + cs = [] :: [constr()], + cmap = dict:new() :: dict(), + fun_map = [] :: typesig_funmap(), + fun_arities = dict:new() :: dict(), + in_match = false :: boolean(), + in_guard = false :: boolean(), + module :: module(), + name_map = dict:new() :: dict(), + next_label :: label(), + self_recs :: [label()], + plt :: dialyzer_plt:plt(), + prop_types = dict:new() :: dict(), + records = dict:new() :: dict(), + opaques = [] :: [erl_types:erl_type()], + scc = [] :: [type_var()], + mfas = [] :: [dialyzer_callgraph:mfa_or_funlbl()] + }). %%----------------------------------------------------------------------------- @@ -448,7 +450,8 @@ traverse(Tree, DefinedVars, State) -> %% Check if a record is constructed. _ -> Arity = length(Fields), - case state__lookup_record(State2, cerl:atom_val(Tag), Arity) of + Records = State2#state.records, + case lookup_record(Records, cerl:atom_val(Tag), Arity) of error -> {State2, TupleType}; {ok, RecType} -> State3 = state__store_conj(TupleType, sub, RecType, State2), @@ -646,8 +649,14 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> PltRes = dialyzer_plt:lookup(Plt, MFA), Opaques = State#state.opaques, Module = State#state.module, + SCCMFAs = State#state.mfas, {FunModule, _, _} = MFA, - case dialyzer_plt:lookup_contract(Plt, MFA) of + Contract = + case lists:member(MFA, SCCMFAs) of + true -> none; + false -> dialyzer_plt:lookup_contract(Plt, MFA) + end, + case Contract of none -> case PltRes of none -> State; @@ -1246,6 +1255,8 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) -> mk_constraint(Var, sub, ArgV)]); get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> %% TODO: Revise this to make it precise for Tag and Arity. + Records = State#state.records, + AllOpaques = State#state.opaques, ArgFun = fun(Map) -> case t_is_atom(true, lookup_type(Dst, Map)) of @@ -1262,10 +1273,8 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> GenRecord = t_tuple([TagType|AnyElems]), case t_atom_vals(TagType) of [TagVal] -> - case state__lookup_record(State, TagVal, - ArityVal - 1) of + case lookup_record(Records, TagVal, ArityVal - 1) of {ok, Type} -> - AllOpaques = State#state.opaques, case t_opaque_match_record(Type, AllOpaques) of [Opaque] -> Opaque; _ -> Type @@ -1287,7 +1296,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> DstFun = fun(Map) -> [TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map), TmpArgTypes2 = - case lists:member(TmpVar, State#state.opaques) of + case lists:member(TmpVar, AllOpaques) of true -> case t_is_integer(TmpArity) of true -> @@ -1297,7 +1306,8 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> true -> case t_atom_vals(TmpTag) of [TmpTagVal] -> - case state__lookup_record(State, TmpTagVal, TmpArityVal - 1) of + case lookup_record(Records, TmpTagVal, + TmpArityVal - 1) of {ok, TmpType} -> case t_is_none(t_inf(TmpType, TmpVar, opaque)) of true -> TmpArgTypes; @@ -1526,9 +1536,10 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); false -> + Opaques = State#state.opaques, Fun = fun(Map) -> [I, T] = ATs = lookup_type_list(Args, Map), - ATs2 = case lists:member(T, State#state.opaques) of + ATs2 = case lists:member(T, Opaques) of true -> [I, erl_types:t_opaque_structure(T)]; false -> ATs end, @@ -1546,7 +1557,7 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, end; get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> GenType = erl_bif_types:type(M, F, A), - Opaques = State#state.opaques, + Opaques = State#state.opaques, case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); false -> @@ -1612,11 +1623,12 @@ get_bif_test_constr(Dst, Arg, Type, State) -> end end, ArgV = ?mk_fun_var(ArgFun, [Dst]), + Opaques = State#state.opaques, DstFun = fun(Map) -> ArgType = lookup_type(Arg, Map), case t_is_none(t_inf(ArgType, Type)) of true -> - case lists:member(ArgType, State#state.opaques) of + case lists:member(ArgType, Opaques) of true -> OpaqueStruct = erl_types:t_opaque_structure(ArgType), case t_is_none(t_inf(OpaqueStruct, Type)) of @@ -1743,33 +1755,29 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, true -> solve_self_recursive(Cs, Map, MapDict, Id, t_none(), State); false -> solve_ref_or_list(Cs, Map, MapDict, State) end, - case Res of - {error, NewMapDict} -> - ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), - Arity = state__fun_arity(Id, State), - FunType = - case state__prop_domain(t_var_name(Id), State) of - error -> t_fun(Arity, t_none()); - {ok, Dom} -> t_fun(Dom, t_none()) - end, - NewMap1 = enter_type(Id, FunType, Map), - NewMap2 = - case state__get_rec_var(Id, State) of - {ok, Var} -> enter_type(Var, FunType, NewMap1); - error -> NewMap1 - end, - {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2}; - {ok, NewMapDict, NewMap} -> - ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), - FunType = lookup_type(Id, NewMap), - NewMap1 = enter_type(Id, FunType, Map), - NewMap2 = - case state__get_rec_var(Id, State) of - {ok, Var} -> enter_type(Var, FunType, NewMap1); - error -> NewMap1 - end, - {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2} - end + {NewMapDict, FunType} = + case Res of + {error, NewMapDict0} -> + ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), + Arity = state__fun_arity(Id, State), + FunType0 = + case state__prop_domain(t_var_name(Id), State) of + error -> t_fun(Arity, t_none()); + {ok, Dom} -> t_fun(Dom, t_none()) + end, + {NewMapDict0, FunType0}; + {ok, NewMapDict0, NewMap} -> + ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), + FunType0 = lookup_type(Id, NewMap), + {NewMapDict0, FunType0} + end, + NewMap1 = enter_type(Id, FunType, Map), + NewMap2 = + case state__get_rec_var(Id, State) of + {ok, Var} -> enter_type(Var, FunType, NewMap1); + error -> NewMap1 + end, + {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2} end; solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id}, Map, MapDict, State) -> @@ -2046,8 +2054,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) -> @@ -2079,10 +2086,15 @@ mk_var_no_lit_list(List) -> %% ============================================================================ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes) -> - NameMap = dict:from_list([{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0]), + List = [{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0], + NameMap = dict:from_list(List), + MFAs = [MFA || {MFA, _Var} <- List], SCC = [mk_var(Fun) || {_MFA, {_Var, Fun}, _Rec} <- SCC0], + SelfRecs = [F || F <- SCC, + dialyzer_callgraph:is_self_rec(t_var_name(F), CallGraph)], #state{callgraph = CallGraph, name_map = NameMap, next_label = NextLabel, - prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC)}. + prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC), + mfas = MFAs, self_recs = ordsets:from_list(SelfRecs)}. state__set_rec_dict(State, RecDict) -> State#state{records = RecDict}. @@ -2092,15 +2104,6 @@ state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) -> erl_types:module_builtin_opaques(M) ++ t_opaque_from_records(RecDict), State#state{opaques = Opaques, module = M}. -state__lookup_record(#state{records = Records}, Tag, Arity) -> - case erl_types:lookup_record(Tag, Arity, Records) of - {ok, Fields} -> - {ok, t_tuple([t_from_term(Tag)| - [FieldType || {_FieldName, FieldType} <- Fields]])}; - error -> - error - end. - state__set_in_match(State, Bool) -> State#state{in_match = Bool}. @@ -2269,14 +2272,12 @@ state__get_cs(Var, #state{cmap = Dict}) -> %% The functions here will not be treated as self recursive. %% These functions will need to be handled as such manually. -state__mark_as_non_self_rec(SCC, #state{non_self_recs = NS} = State) -> - State#state{non_self_recs = ordsets:union(NS, ordsets:from_list(SCC))}. +state__mark_as_non_self_rec(SCC, #state{self_recs = SelfRecs} = State) -> + %% TODO: Check if the result is always empty and just set it to [] if so. + State#state{self_recs = ordsets:subtract(SelfRecs, ordsets:from_list(SCC))}. -state__is_self_rec(Fun, #state{callgraph = CallGraph, non_self_recs = NS}) -> - case ordsets:is_element(Fun, NS) of - true -> false; - false -> dialyzer_callgraph:is_self_rec(t_var_name(Fun), CallGraph) - end. +state__is_self_rec(Fun, #state{self_recs = SelfRecs}) -> + ordsets:is_element(Fun, SelfRecs). state__store_funs(Vars0, Funs0, #state{fun_map = Map} = State) -> debug_make_name_map(Vars0, Funs0), @@ -2690,6 +2691,15 @@ find_constraint(Tuple, [#constraint_list{list = List}|Cs]) -> find_constraint(Tuple, [_|Cs]) -> find_constraint(Tuple, Cs). +lookup_record(Records, Tag, Arity) -> + case erl_types:lookup_record(Tag, Arity, Records) of + {ok, Fields} -> + {ok, t_tuple([t_from_term(Tag)| + [FieldType || {_FieldName, FieldType} <- Fields]])}; + error -> + error + end. + %% ============================================================================ %% %% Pretty printer and debug facilities. diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile index 47deb17f1d..6a1abce943 100644 --- a/lib/dialyzer/test/Makefile +++ b/lib/dialyzer/test/Makefile @@ -30,5 +30,5 @@ release_tests_spec: $(INSTALL_DATA) $(AUXILIARY_FILES) $(RELSYSDIR) @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) cd $(RELSYSDIR);\ - erl -make;\ + erlc dialyzer_common.erl file_utils.erl;\ erl -noshell -run dialyzer_common create_all_suites -s erlang halt diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs index da498c225d..33d135048e 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/callbacks_and_specs @@ -1,5 +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 {'noreply',#state{parent::'undefined' | pid(),status::'closed' | 'init' | 'open',subscribe::[{pid(),integer()}],counter::integer()}} | {'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: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 index 2afb5db133..e646eea383 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_event_incorrect_return +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_event_incorrect_return @@ -1,2 +1,2 @@ -gen_event_incorrect_return.erl:16: The inferred return type of init/1 ('error') has nothing in common with {'ok',_} | {'ok',_,'hibernate'}, which is the expected return type for the callback of gen_event behaviour +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/sample_behaviour b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour index a38e662ccf..8cecabccaa 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour @@ -1,9 +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') +sample_callback_wrong.erl:16: 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:17: 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:18: 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:20: 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:20: 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:22: 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:22: 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:4: Undefined callback function sample_callback_1/0 (behaviour 'sample_behaviour') 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/custom_sup.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/custom_sup.erl new file mode 100644 index 0000000000..8ec84d798f --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/custom_sup.erl @@ -0,0 +1,37 @@ +%%% Dialyzer was giving a warning with this input because of a bug in the +%%% substitution of remote types in specs. Remote types in the first element of +%%% a tuple would not update the tuple's tag set and we could end up with a +%%% non-normalized representation. +%%% +%%% Reported by Damian DobroczyĆski on 29/02/2012 + +-module(custom_sup). + +-behavior(supervisor). + +-export([init/1]). + +-spec init(atom()) -> + {ok, {{supervisor:strategy(), non_neg_integer(), non_neg_integer()}, + [supervisor:child_spec()]}} | ignore. + +init(StorageName) -> + Strategy = {one_for_all, 100, 1}, + %% get application-wide storage parameters + case application:get_env(storage) of + undefined -> + ignore; + {ok, Storage} -> + BackendId = proplists:get_value(backend, Storage), + BackendArgs = proplists:get_value(args, Storage), + if + (BackendId =:= undefined) orelse (BackendArgs =:= undefined) -> + ignore; + true -> + {ok, {Strategy, + [{id1, {a_module, start_link, []}, + permanent, 5000, worker, [a_module]}, + {id2, {another_module, start_link, []}, + permanent, 5000, worker, [another_module]}]}} + end + end. 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 index 02a063fab7..430494c48c 100644 --- 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 @@ -1,6 +1,7 @@ -module(sample_callback_wrong). --behaviour(sample_behaviour). +%% This attribute uses the american spelling of 'behaviour'. +-behavior(sample_behaviour). -export([ % sample_callback_1/0, 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/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl index 51766a4604..d2b1026c06 100644 --- a/lib/dialyzer/test/dialyzer_common.erl +++ b/lib/dialyzer/test/dialyzer_common.erl @@ -216,10 +216,13 @@ get_suites(Dir) -> end. suffix(String, Suffix) -> - Index = string:rstr(String, Suffix), - case string:substr(String, Index) =:= Suffix of - true -> {yes, string:sub_string(String,1,Index-1)}; - false -> no + case string:rstr(String, Suffix) of + 0 -> no; + Index -> + case string:substr(String, Index) =:= Suffix of + true -> {yes, string:sub_string(String,1,Index-1)}; + false -> no + end end. -spec create_suite(string()) -> 'ok'. diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques new file mode 100644 index 0000000000..18ece8820c --- /dev/null +++ b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques @@ -0,0 +1,2 @@ + +multiple_wrong_opaques.erl:5: Invalid type specification for function multiple_wrong_opaques:weird/1. The success typing is ('gazonk') -> 42 diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl new file mode 100644 index 0000000000..9e695cec1d --- /dev/null +++ b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl @@ -0,0 +1,8 @@ +-module(multiple_wrong_opaques). + +-export([weird/1]). + +-spec weird(dict() | gb_tree()) -> 42. + +weird(gazonk) -> 42. + diff --git a/lib/dialyzer/test/options1_SUITE_data/results/compiler b/lib/dialyzer/test/options1_SUITE_data/results/compiler index e82087ae86..6399e3e36b 100644 --- a/lib/dialyzer/test/options1_SUITE_data/results/compiler +++ b/lib/dialyzer/test/options1_SUITE_data/results/compiler @@ -20,6 +20,7 @@ cerl_inline.erl:2333: The pattern 'true' can never match the type 'false' cerl_inline.erl:2355: The pattern 'true' can never match the type 'false' cerl_inline.erl:238: The pattern 'true' can never match the type 'false' cerl_inline.erl:2436: Function filename/1 will never be called +cerl_inline.erl:244: Function counter_stats/0 will never be called cerl_inline.erl:2700: The pattern 'true' can never match the type 'false' cerl_inline.erl:2730: The pattern <{F, L, D}, Vs> can never match the type <[1..255,...],[any()]> cerl_inline.erl:2738: The pattern <{F, L, D}, Vs> can never match the type <[1..255,...],[any()]> diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/inets b/lib/dialyzer/test/r9c_SUITE_data/results/inets index 6b16dba2ff..773525eb7f 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/inets +++ b/lib/dialyzer/test/r9c_SUITE_data/results/inets @@ -8,11 +8,11 @@ http_lib.erl:424: The variable _ can never match since previous clauses complete 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_handler.erl:676: Function format_time/0 will never be called 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 @@ -24,8 +24,7 @@ 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',_}},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: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_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',_,_} @@ -40,10 +39,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/small_SUITE_data/results/cerl_hipeify b/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify index 87bf6f309f..06dc0d63ee 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify +++ b/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify @@ -1,4 +1,4 @@ cerl_hipeify.erl:370: Function will never be called -cerl_hipeify.erl:370: Guard test fun((none()) -> none()) =:= F::{_,_,_} | {_,_,_,_} | {_,_,_,_,_} | {_,_,_,_,_,_} | {_,_,_,_,_,_,_} can never succeed +cerl_hipeify.erl:370: Guard test fun((none()) -> no_return()) =:= F::{_,_,_} | {_,_,_,_} | {_,_,_,_,_} | {_,_,_,_,_,_} | {_,_,_,_,_,_,_} can never succeed cerl_hipeify.erl:641: Function env__new_function_name/2 will never be called diff --git a/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 b/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 index 7e9972ad98..142e4b2c37 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 +++ b/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 @@ -1,4 +1,4 @@ inf_loop2.erl:18: Function test/0 has no local return inf_loop2.erl:19: The call lists:reverse('gazonk') will never return since it differs in the 1st argument from the success typing arguments: ([any()]) -inf_loop2.erl:22: Function loop/0 has no local return +inf_loop2.erl:22: Function loop/0 will never be called diff --git a/lib/dialyzer/test/small_SUITE_data/results/no_local_return b/lib/dialyzer/test/small_SUITE_data/results/no_local_return new file mode 100644 index 0000000000..6ca1ed51d8 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/results/no_local_return @@ -0,0 +1,3 @@ + +no_local_return.erl:11: Function bar/1 will never be called +no_local_return.erl:8: Function foo/0 will never be called 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/small_SUITE_data/src/no_local_return.erl b/lib/dialyzer/test/small_SUITE_data/src/no_local_return.erl new file mode 100644 index 0000000000..4e1a0b015a --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/no_local_return.erl @@ -0,0 +1,12 @@ +-module(no_local_return). + +%% NOTE: No function is exported. Dialyzer produced a bogus +%% 'Function foo/0 has no local return' warning +%% when in fact typer was finding correct return values for both +%% these functions. + +foo() -> + bar(42). + +bar(X) -> + lists:duplicate(X, gazonk). diff --git a/lib/dialyzer/test/small_SUITE_data/src/on_load.erl b/lib/dialyzer/test/small_SUITE_data/src/on_load.erl new file mode 100644 index 0000000000..16533a9caa --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/on_load.erl @@ -0,0 +1,11 @@ +%%% This is to ensure that "on_load" functions are never reported as unused. + +-module(on_load). + +-export([foo/0]). + +-on_load(bar/0). + +foo() -> ok. + +bar() -> ok. 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. diff --git a/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_1 b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_1 new file mode 100644 index 0000000000..de416455e2 --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_1 @@ -0,0 +1,2 @@ + +nowarn_unused_function_1.erl:17: Function f3/1 will never be called diff --git a/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_2 b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_2 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_2 diff --git a/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_3 b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_3 new file mode 100644 index 0000000000..8ae78673d5 --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/results/nowarn_unused_function_3 @@ -0,0 +1,3 @@ + +nowarn_unused_function_3.erl:12: Function f2/1 will never be called +nowarn_unused_function_3.erl:9: Function f1/1 will never be called diff --git a/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_1.erl b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_1.erl new file mode 100644 index 0000000000..fcce532f73 --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_1.erl @@ -0,0 +1,18 @@ +%% Test that option 'nowarn_unused_funcion' works similarly in +%% Dialyzer as in the compiler. + +-module(nowarn_unused_function_1). + +-compile(warn_unused_function). + +-compile({nowarn_unused_function,f1/1}). +f1(_) -> + a. + +-compile({nowarn_unused_function,[{f2,1}]}). +f2(_) -> + a. + +-compile({warn_unused_function,[{f3,1}]}). +f3(_) -> + a. diff --git a/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_2.erl b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_2.erl new file mode 100644 index 0000000000..9bc3ab14ea --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_2.erl @@ -0,0 +1,18 @@ +%% Test that option 'nowarn_unused_funcion' works similarly in +%% Dialyzer as in the compiler. + +-module(nowarn_unused_function_2). + +-compile(nowarn_unused_function). + +-compile({warn_unused_function,f1/1}). +f1(_) -> + a. + +-compile({warn_unused_function,[{f2,1}]}). +f2(_) -> + a. + +-compile({nowarn_unused_function,[{f3,1}]}). +f3(_) -> + a. diff --git a/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_3.erl b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_3.erl new file mode 100644 index 0000000000..604c5e436b --- /dev/null +++ b/lib/dialyzer/test/user_SUITE_data/src/nowarn_unused_function_3.erl @@ -0,0 +1,16 @@ +%% Test that option 'nowarn_unused_funcion' works similarly in +%% Dialyzer as in the compiler. + +-module(nowarn_unused_function_3). + +-compile({warn_unused_function,[{f1,1},{f2,1}]}). +-compile({nowarn_unused_function,[{f3,1}]}). + +f1(_) -> + a. + +f2(_) -> + a. + +f3(_) -> + a. diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index a7e82b54ce..622e51b859 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 2.4.4 +DIALYZER_VSN = 2.5 |