diff options
Diffstat (limited to 'lib/dialyzer')
-rw-r--r-- | lib/dialyzer/RELEASE_NOTES | 7 | ||||
-rw-r--r-- | lib/dialyzer/doc/manual.txt | 12 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer.erl | 30 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer.hrl | 2 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_analysis_callgraph.erl | 44 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl.erl | 122 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl_parse.erl | 44 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_contracts.erl | 29 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_gui.erl | 14 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_gui_wx.erl | 14 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_options.erl | 19 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_plt.erl | 65 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_typesig.erl | 19 |
13 files changed, 299 insertions, 122 deletions
diff --git a/lib/dialyzer/RELEASE_NOTES b/lib/dialyzer/RELEASE_NOTES index a05b3ac52b..08f274a996 100644 --- a/lib/dialyzer/RELEASE_NOTES +++ b/lib/dialyzer/RELEASE_NOTES @@ -5,6 +5,13 @@ Version 2.x.x (in Erlang/OTP R14B01) ------------------------------------ + - Fixed pretty rare infinite loop when refining the types of an SCC whose + functions all returned none() (thanks to Stavros Aronis). + - Fixed pretty rare crash when taking the infimum of two tuple_sets. + - Fixed pretty rare crash when using parameterized types containing unbound + variables (thanks to Nicolas Trangez for reporting it). + - Deeper unfolding of recursive types (thanks to Maria Christakis). + - Fixed some incomplete and erroneous specs in modules of kernel and stdlib. - Fixed problems in the handling of remote types in records used as types (thanks to Nico Kruber for the report and to Maria Christakis for the fix). - Fixed handling of nested opaque types (thanks to Thorsten Schuett for diff --git a/lib/dialyzer/doc/manual.txt b/lib/dialyzer/doc/manual.txt index 470ddd6c73..d9cb52f722 100644 --- a/lib/dialyzer/doc/manual.txt +++ b/lib/dialyzer/doc/manual.txt @@ -123,9 +123,10 @@ The exit status of the command line version is: Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose] - [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]* - [--output_plt file] [-Wwarn]* [--src] [--gui | --wx] - [files_or_dirs] [-r dirs] [--apps applications] [-o outfile] + [-pa dir]* [--plt plt] [--plts plts] [-Ddefine]* + [-I include_dir]* [--output_plt file] [-Wwarn]* + [--src] [--gui | --wx] [files_or_dirs] [-r dirs] + [--apps applications] [-o outfile] [--build_plt] [--add_to_plt] [--remove_from_plt] [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] [--no_native] @@ -167,6 +168,10 @@ Options: --plt plt Use the specified plt as the initial plt (if the plt was built during setup the files will be checked for consistency) + --plts plts + Merges the specified plts to create the initial plt -- requires + that the plts are disjoint (i.e., do not have any module + appearing in more than one plt) -Wwarn A family of options which selectively turn on/off warnings (for help on the names of warnings use dialyzer -Whelp) @@ -294,6 +299,7 @@ Option :: {files, [Filename :: string()]} | {defines, [{Macro :: atom(), Value :: term()}]} | {from, src_code | byte_code} %% Defaults to byte_code | {init_plt, FileName :: string()} %% If changed from default + | {plts, [FileName :: string()]} %% If changed from default | {include_dirs, [DirName :: string()]} | {output_file, FileName :: string()} | {output_plt, FileName :: string()} diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index d8fd073ca6..471f9fccd2 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -106,27 +106,35 @@ cl_print_plt_info(Opts) -> end, doit(F). -print_plt_info(#options{init_plt = PLT, output_file = OutputFile}) -> +print_plt_info(#options{init_plts = PLTs, output_file = OutputFile}) -> + PLTInfo = get_plt_info(PLTs), + do_print_plt_info(PLTInfo, OutputFile). + +get_plt_info([PLT|PLTs]) -> String = case dialyzer_plt:included_files(PLT) of {ok, Files} -> - io_lib:format("The PLT ~s includes the following files:\n~p\n", + io_lib:format("The PLT ~s includes the following files:\n~p\n\n", [PLT, Files]); {error, read_error} -> - Msg = io_lib:format("Could not read the PLT file ~p\n", [PLT]), + Msg = io_lib:format("Could not read the PLT file ~p\n\n", [PLT]), throw({dialyzer_error, Msg}); {error, no_such_file} -> - Msg = io_lib:format("The PLT file ~p does not exist\n", [PLT]), + Msg = io_lib:format("The PLT file ~p does not exist\n\n", [PLT]), throw({dialyzer_error, Msg}) end, + String ++ get_plt_info(PLTs); +get_plt_info([]) -> "". + +do_print_plt_info(PLTInfo, OutputFile) -> case OutputFile =:= none of true -> - io:format("~s", [String]), + io:format("~s", [PLTInfo]), ?RET_NOTHING_SUSPICIOUS; false -> case file:open(OutputFile, [write]) of {ok, FileDesc} -> - io:format(FileDesc, "~s", [String]), + io:format(FileDesc, "~s", [PLTInfo]), ok = file:close(FileDesc), ?RET_NOTHING_SUSPICIOUS; {error, Reason} -> @@ -225,6 +233,8 @@ plt_info(Plt) -> %% Machinery %%----------- +-type doit_ret() :: {'ok', dial_ret()} | {'error', string()}. + doit(F) -> try {ok, F()} @@ -233,13 +243,17 @@ doit(F) -> {error, lists:flatten(Msg)} end. +-spec cl_error(string()) -> no_return(). + cl_error(Msg) -> cl_halt({error, Msg}, #options{}). +-spec gui_halt(doit_ret(), #options{}) -> no_return(). + gui_halt(R, Opts) -> cl_halt(R, Opts#options{report_mode = quiet}). --spec cl_halt({'ok',dial_ret()} | {'error',string()}, #options{}) -> no_return(). +-spec cl_halt(doit_ret(), #options{}) -> no_return(). cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{report_mode = quiet}) -> halt(R); @@ -379,7 +393,7 @@ message_to_string({spec_missing_fun, [M, F, A]}) -> [M, F, A]); %%----- Warnings for opaque type violations ------------------- message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) -> - io_lib:format("The call ~w:~w~s contains ~s argument when ~s\n", + io_lib:format("The call ~w:~w~s contains ~s when ~s\n", [M, F, Args, form_positions(ArgNs), form_expected(ExpArgs)]); message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) -> io_lib:format("The call ~w:~w~s does not have ~s\n", diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index 2da8ed2e5d..1d98574585 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -129,7 +129,7 @@ defines = [] :: [dial_define()], from = byte_code :: start_from(), get_warnings = maybe :: boolean() | 'maybe', - init_plt = none :: 'none' | file:filename(), + init_plts = [] :: [file:filename()], include_dirs = [] :: [file:filename()], output_plt = none :: 'none' | file:filename(), legal_warnings = ordsets:new() :: ordset(dial_warn_tag()), diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 3438cc8c7e..abad1f3a75 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_analysis_callgraph.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 5 Apr 2005 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -32,7 +32,7 @@ -include("dialyzer.hrl"). --record(analysis_state, +-record(analysis_state, { codeserver :: dialyzer_codeserver:codeserver(), analysis_type = succ_typings :: anal_type(), @@ -44,7 +44,7 @@ plt :: dialyzer_plt:plt(), start_from = byte_code :: start_from(), use_contracts = true :: boolean(), - behaviours = {false,[]} :: {boolean(),[atom()]} + behaviours = {false,[]} :: {boolean(),[atom()]} }). -record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}). @@ -83,10 +83,10 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State, send_warnings(Parent, SendWarnings) end, loop(State, Analysis, ExtCalls); - {AnalPid, cserver, CServer, Plt} -> + {AnalPid, cserver, CServer, Plt} -> send_codeserver_plt(Parent, CServer, Plt), loop(State, Analysis, ExtCalls); - {AnalPid, done, Plt, DocPlt} -> + {AnalPid, done, Plt, DocPlt} -> case ExtCalls =:= none of true -> send_analysis_done(Parent, Plt, DocPlt); @@ -176,7 +176,7 @@ analysis_start(Parent, Analysis) -> NonExportsList = sets:to_list(NonExports), Plt3 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), Plt4 = dialyzer_plt:delete_contract_list(Plt3, NonExportsList), - send_codeserver_plt(Parent, CServer, State3#analysis_state.plt), + send_codeserver_plt(Parent, CServer, State3#analysis_state.plt), send_analysis_done(Parent, Plt4, State3#analysis_state.doc_plt). analyze_callgraph(Callgraph, State) -> @@ -229,24 +229,24 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, {error, Reason} -> {TmpCG, TmpCServer, [{File, Reason}|TmpFailed], TmpNoWarn, TmpMods}; - {ok, NewCG, NoWarn, NewCServer, Mod} -> + {ok, NewCG, NoWarn, NewCServer, Mod} -> {NewCG, NewCServer, TmpFailed, NoWarn++TmpNoWarn, [Mod|TmpMods]} end end; byte_code -> - fun(File, {TmpCG, TmpCServer, TmpFailed, TmpNoWarn, TmpMods}) -> + fun(File, {TmpCG, TmpCServer, TmpFailed, TmpNoWarn, TmpMods}) -> case compile_byte(File, TmpCG, TmpCServer, UseContracts) of {error, Reason} -> {TmpCG, TmpCServer, [{File, Reason}|TmpFailed], TmpNoWarn, TmpMods}; - {ok, NewCG, NoWarn, NewCServer, Mod} -> + {ok, NewCG, NoWarn, NewCServer, Mod} -> {NewCG, NewCServer, TmpFailed, NoWarn++TmpNoWarn, [Mod|TmpMods]} end end end, - {NewCallgraph1, NewCServer, Failed, NoWarn, Modules} = + {NewCallgraph1, NewCServer, Failed, NoWarn, Modules} = lists:foldl(Fun, {Callgraph, CServer, [], [], []}, Files), case Failed =:= [] of true -> @@ -255,7 +255,7 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, lists:foldl(fun({Mod, F}, Dict) -> dict:append(Mod, F, Dict) end, dict:new(), NewFiles), check_for_duplicate_modules(ModDict); - false -> + false -> Msg = io_lib:format("Could not scan the following file(s): ~p", [lists:flatten(Failed)]), exit({error, Msg}) @@ -268,14 +268,14 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, if UnknownBehaviours =:= [] -> ok; true -> send_unknown_behaviours(Parent, UnknownBehaviours) end, - State1 = State#analysis_state{behaviours = {BehChk,KnownBehaviours}}, + State1 = State#analysis_state{behaviours = {BehChk, KnownBehaviours}}, NewCallgraph2 = cleanup_callgraph(State1, NewCServer, NewCallgraph1, Modules), {T3, _} = statistics(runtime), Msg2 = io_lib:format("done in ~.2f secs\n", [(T3-T2)/1000]), - send_log(Parent, Msg2), + send_log(Parent, Msg2), {NewCallgraph2, sets:from_list(NoWarn), NewCServer}. -cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, +cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, codeserver = CodeServer, behaviours = {BehChk, KnownBehaviours} }, @@ -298,9 +298,9 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, not dialyzer_plt:contains_mfa(InitPlt, To)], {BadCalls1, RealExtCalls} = if ExtCalls1 =:= [] -> {[], []}; - true -> + true -> ModuleSet = sets:from_list(Modules), - lists:partition(fun({_From, {M, _F, _A}}) -> + lists:partition(fun({_From, {M, _F, _A}}) -> sets:is_element(M, ModuleSet) orelse dialyzer_plt:contains_module(InitPlt, M) end, ExtCalls1) @@ -367,14 +367,14 @@ compile_byte(File, Callgraph, CServer, UseContracts) -> case dialyzer_utils:get_record_and_type_info(AbstrCode) of {error, _} = Error -> Error; {ok, RecInfo} -> - CServer1 = + CServer1 = dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), case UseContracts of true -> case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of {error, _} = Error -> Error; {ok, SpecInfo} -> - CServer2 = + CServer2 = dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo, CServer1), store_core(Mod, Core, NoWarn, Callgraph, CServer2) @@ -455,8 +455,12 @@ expand_files([File|Left], Ext, FileAcc) -> case filelib:is_dir(File) of true -> {ok, List} = file:list_dir(File), - NewFiles = - [filename:join(File, X) || X <- List, filename:extension(X) =:= Ext], + NewFiles = lists:foldl(fun (X, Acc) -> + case filename:extension(X) =:= Ext of + true -> [filename:join(File, X)|Acc]; + false -> Acc + end + end, FileAcc, List), expand_files(Left, Ext, NewFiles); false -> expand_files(Left, Ext, [File|FileAcc]) diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index 616e2465dc..1987c1732c 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -81,11 +81,15 @@ build_plt(Opts) -> init_opts_for_build(Opts) -> case Opts#options.output_plt =:= none of true -> - case Opts#options.init_plt of - none -> Opts#options{init_plt = none, output_plt = get_default_plt()}; - Plt -> Opts#options{init_plt = none, output_plt = Plt} + case Opts#options.init_plts of + [] -> Opts#options{output_plt = get_default_output_plt()}; + [Plt] -> Opts#options{init_plts = [], output_plt = Plt}; + Plts -> + Msg = io_lib:format("Could not build multiple PLT files: ~s\n", + [format_plts(Plts)]), + error(Msg) end; - false -> Opts#options{init_plt = none} + false -> Opts#options{init_plts = []} end. %%-------------------------------------------------------------------- @@ -98,39 +102,58 @@ add_to_plt(Opts) -> init_opts_for_add(Opts) -> case Opts#options.output_plt =:= none of true -> - case Opts#options.init_plt of - none -> Opts#options{output_plt = get_default_plt(), - init_plt = get_default_plt()}; - Plt -> Opts#options{output_plt = Plt} + case Opts#options.init_plts of + [] -> Opts#options{output_plt = get_default_output_plt(), + init_plts = get_default_init_plt()}; + [Plt] -> Opts#options{output_plt = Plt}; + Plts -> + Msg = io_lib:format("Could not add to multiple PLT files: ~s\n", + [format_plts(Plts)]), + error(Msg) end; false -> - case Opts#options.init_plt =:= none of - true -> Opts#options{init_plt = get_default_plt()}; + case Opts#options.init_plts =:= [] of + true -> Opts#options{init_plts = get_default_init_plt()}; false -> Opts end end. %%-------------------------------------------------------------------- -check_plt(Opts) -> +check_plt(#options{init_plts = []} = Opts) -> Opts1 = init_opts_for_check(Opts), - report_check(Opts), - plt_common(Opts1, [], []). + report_check(Opts1), + plt_common(Opts1, [], []); +check_plt(#options{init_plts = Plts} = Opts) -> + check_plt_aux(Plts, Opts). + +check_plt_aux([_] = Plt, Opts) -> + Opts1 = Opts#options{init_plts = Plt}, + Opts2 = init_opts_for_check(Opts1), + 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). init_opts_for_check(Opts) -> - Plt = - case Opts#options.init_plt of - none -> get_default_plt(); - Plt0 -> Plt0 + InitPlt = + case Opts#options.init_plts of + []-> get_default_init_plt(); + Plt -> Plt end, + [OutputPlt] = InitPlt, Opts#options{files = [], files_rec = [], analysis_type = plt_check, defines = [], from = byte_code, - init_plt = Plt, + init_plts = InitPlt, include_dirs = [], - output_plt = Plt, + output_plt = OutputPlt, use_contracts = true }. @@ -144,21 +167,25 @@ remove_from_plt(Opts) -> init_opts_for_remove(Opts) -> case Opts#options.output_plt =:= none of true -> - case Opts#options.init_plt of - none -> Opts#options{output_plt = get_default_plt(), - init_plt = get_default_plt()}; - Plt -> Opts#options{output_plt = Plt} + case Opts#options.init_plts of + [] -> Opts#options{output_plt = get_default_output_plt(), + init_plts = get_default_init_plt()}; + [Plt] -> Opts#options{output_plt = Plt}; + Plts -> + Msg = io_lib:format("Could not remove from multiple PLT files: ~s\n", + [format_plts(Plts)]), + error(Msg) end; false -> - case Opts#options.init_plt =:= none of - true -> Opts#options{init_plt = get_default_plt()}; + case Opts#options.init_plts =:= [] of + true -> Opts#options{init_plts = get_default_init_plt()}; false -> Opts end end. %%-------------------------------------------------------------------- -plt_common(Opts, RemoveFiles, AddFiles) -> +plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) -> case check_plt(Opts, RemoveFiles, AddFiles) of ok -> case Opts#options.report_mode of @@ -174,7 +201,7 @@ plt_common(Opts, RemoveFiles, AddFiles) -> report_failed_plt_check(Opts, DiffMd5), {AnalFiles, RemovedMods, ModDeps1} = expand_dependent_modules(Md5, DiffMd5, ModDeps), - Plt = clean_plt(Opts#options.init_plt, RemovedMods), + Plt = clean_plt(InitPlt, RemovedMods), case AnalFiles =:= [] of true -> %% Only removed stuff. Just write the PLT. @@ -186,19 +213,19 @@ plt_common(Opts, RemoveFiles, AddFiles) -> end; {error, no_such_file} -> Msg = io_lib:format("Could not find the PLT: ~s\n~s", - [Opts#options.init_plt, default_plt_error_msg()]), + [InitPlt, default_plt_error_msg()]), error(Msg); {error, not_valid} -> Msg = io_lib:format("The file: ~s is not a valid PLT file\n~s", - [Opts#options.init_plt, default_plt_error_msg()]), + [InitPlt, default_plt_error_msg()]), error(Msg); {error, read_error} -> Msg = io_lib:format("Could not read the PLT: ~s\n~s", - [Opts#options.init_plt, default_plt_error_msg()]), + [InitPlt, default_plt_error_msg()]), error(Msg); {error, {no_file_to_remove, F}} -> Msg = io_lib:format("Could not remove the file ~s from the PLT: ~s\n", - [F, Opts#options.init_plt]), + [F, InitPlt]), error(Msg) end. @@ -218,8 +245,7 @@ default_plt_error_msg() -> %%-------------------------------------------------------------------- -check_plt(Opts, RemoveFiles, AddFiles) -> - Plt = Opts#options.init_plt, +check_plt(#options{init_plts = [Plt]} = Opts, RemoveFiles, AddFiles) -> case dialyzer_plt:check_plt(Plt, RemoveFiles, AddFiles) of {old_version, _MD5} = OldVersion -> report_old_version(Opts), @@ -234,14 +260,14 @@ check_plt(Opts, RemoveFiles, AddFiles) -> %%-------------------------------------------------------------------- -report_check(#options{report_mode = ReportMode, init_plt = InitPlt}) -> +report_check(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> case ReportMode of quiet -> ok; _ -> io:format(" Checking whether the PLT ~s is up-to-date...", [InitPlt]) end. -report_old_version(#options{report_mode = ReportMode, init_plt = InitPlt}) -> +report_old_version(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> case ReportMode of quiet -> ok; _ -> @@ -264,7 +290,7 @@ report_failed_plt_check(#options{analysis_type = AnalType, report_analysis_start(#options{analysis_type = Type, report_mode = ReportMode, - init_plt = InitPlt, + init_plts = InitPlts, output_plt = OutputPlt}) -> case ReportMode of quiet -> ok; @@ -272,6 +298,7 @@ report_analysis_start(#options{analysis_type = Type, io:format(" "), case Type of plt_add -> + [InitPlt] = InitPlts, case InitPlt =:= OutputPlt of true -> io:format("Adding information to ~s...", [OutputPlt]); false -> io:format("Adding information from ~s to ~s...", @@ -282,6 +309,7 @@ report_analysis_start(#options{analysis_type = Type, plt_check -> io:format("Rebuilding the information in ~s...", [OutputPlt]); plt_remove -> + [InitPlt] = InitPlts, case InitPlt =:= OutputPlt of true -> io:format("Removing information from ~s...", [OutputPlt]); false -> io:format("Removing information from ~s to ~s...", @@ -320,16 +348,28 @@ report_md5_diff(List) -> %%-------------------------------------------------------------------- -get_default_plt() -> +get_default_init_plt() -> + [dialyzer_plt:get_default_plt()]. + +get_default_output_plt() -> dialyzer_plt:get_default_plt(). %%-------------------------------------------------------------------- +format_plts([Plt]) -> Plt; +format_plts([Plt|Plts]) -> + Plt ++ ", " ++ format_plts(Plts). + +%%-------------------------------------------------------------------- + do_analysis(Options) -> Files = get_files_from_opts(Options), - case Options#options.init_plt of - none -> do_analysis(Files, Options, dialyzer_plt:new(), none); - File -> do_analysis(Files, Options, dialyzer_plt:from_file(File), none) + case Options#options.init_plts of + [] -> do_analysis(Files, Options, dialyzer_plt:new(), none); + PltFiles -> + Plts = [dialyzer_plt:from_file(F) || F <- PltFiles], + Plt = dialyzer_plt:merge_plts_or_report_conflicts(PltFiles, Plts), + do_analysis(Files, Options, Plt, none) end. do_analysis(Files, Options, Plt, PltInfo) -> @@ -559,7 +599,7 @@ cl_loop(State, LogCache) -> cl_loop(State, LogCache) end. --spec failed_anal_msg(string(), [_]) -> string(). +-spec failed_anal_msg(string(), [_]) -> nonempty_string(). failed_anal_msg(Reason, LogCache) -> Msg = "Analysis failed with error: " ++ Reason ++ "\n", diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index 0160b84abc..2f9e577544 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -138,11 +138,17 @@ cl(["-pa", Path|T]) -> true -> cl(T); {error, _} -> error("Bad directory for -pa: "++Path) end; -cl(["--plt", PLT|T]) -> - put(dialyzer_init_plt, PLT), - cl(T); cl(["--plt"]) -> error("No plt specified for --plt"); +cl(["--plt", PLT|T]) -> + put(dialyzer_init_plts, [PLT]), + cl(T); +cl(["--plts"]) -> + error("No plts specified for --plts"); +cl(["--plts"|T]) -> + {PLTs, NewT} = get_plts(T, []), + put(dialyzer_init_plts, PLTs), + cl(NewT); cl(["-q"|T]) -> put(dialyzer_options_report_mode, quiet), cl(T); @@ -284,7 +290,7 @@ common_options() -> [{defines, get(dialyzer_options_defines)}, {from, get(dialyzer_options_from)}, {include_dirs, get(dialyzer_include)}, - {init_plt, get(dialyzer_init_plt)}, + {plts, get(dialyzer_init_plts)}, {output_plt, get(dialyzer_output_plt)}, {report_mode, get(dialyzer_options_report_mode)}, {use_spec, get(dialyzer_options_use_contracts)}, @@ -309,6 +315,13 @@ get_lib_dir([], Acc) -> %%----------------------------------------------------------------------- +get_plts(["--"|T], Acc) -> {lists:reverse(Acc), T}; +get_plts(["-"++_Opt = H|T], Acc) -> {lists:reverse(Acc), [H|T]}; +get_plts([H|T], Acc) -> get_plts(T, [H|Acc]); +get_plts([], Acc) -> {lists:reverse(Acc), []}. + +%%----------------------------------------------------------------------- + help_warnings() -> S = warning_options_msg(), io:put_chars(S), @@ -316,9 +329,10 @@ help_warnings() -> help_message() -> S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose] - [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]* - [--output_plt file] [-Wwarn]* [--src] [--gui | --wx] - [files_or_dirs] [-r dirs] [--apps applications] [-o outfile] + [-pa dir]* [--plt plt] [--plts plts] [-Ddefine]* + [-I include_dir]* [--output_plt file] [-Wwarn]* + [--src] [--gui | --wx] [files_or_dirs] [-r dirs] + [--apps applications] [-o outfile] [--build_plt] [--add_to_plt] [--remove_from_plt] [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] [--no_native] @@ -362,6 +376,10 @@ Options: --plt plt Use the specified plt as the initial plt (if the plt was built during setup the files will be checked for consistency) + --plts plts + Merges the specified plts to create the initial plt -- requires + that the plts are disjoint (i.e., do not have any module + appearing in more than one plt) -Wwarn A family of options which selectively turn on/off warnings (for help on the names of warnings use dialyzer -Whelp) @@ -378,12 +396,12 @@ Options: --build_plt The analysis starts from an empty plt and creates a new one from the files specified with -c and -r. Only works for beam files. - Use --plt or --output_plt to override the default plt location. + Use --plt(s) or --output_plt to override the default plt location. --add_to_plt The plt is extended to also include the files specified with -c and -r. - Use --plt to specify wich plt to start from, and --output_plt to - specify where to put the plt. Note that the analysis might include - files from the plt if they depend on the new files. + Use --plt(s) to specify wich plt to start from, and --output_plt to + specify where to put the plt. Note that the analysis might include + files from the plt if they depend on the new files. This option only works with beam files. --remove_from_plt The information from the files specified with -c and -r is removed @@ -396,8 +414,8 @@ Options: Skip the plt check when running Dialyzer. Useful when working with installed plts that never change. --plt_info - Makes Dialyzer print information about the plt and then quit. The plt - can be specified with --plt. + Makes Dialyzer print information about the plt and then quit. The plt + can be specified with --plt(s). --get_warnings Makes Dialyzer emit warnings even when manipulating the plt. Only emits warnings for files that are actually analyzed. diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index bf80c6f470..bcdcf2685d 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -163,20 +163,23 @@ process_contract_remote_types(CodeServer) -> check_contracts(Contracts, Callgraph, FunTypes) -> FoldFun = fun(Label, Type, NewContracts) -> - {ok, {M,F,A} = MFA} = dialyzer_callgraph:lookup_name(Label, Callgraph), - case orddict:find(MFA, Contracts) of - {ok, {_FileLine, Contract}} -> - case check_contract(Contract, Type) of - ok -> - case erl_bif_types:is_known(M, F, A) of - true -> - %% Disregard the contracts since - %% this is a known function. - NewContracts; - false -> - [{MFA, Contract}|NewContracts] + case dialyzer_callgraph:lookup_name(Label, Callgraph) of + {ok, {M,F,A} = MFA} -> + case orddict:find(MFA, Contracts) of + {ok, {_FileLine, Contract}} -> + case check_contract(Contract, Type) of + ok -> + case erl_bif_types:is_known(M, F, A) of + true -> + %% Disregard the contracts since + %% this is a known function. + NewContracts; + false -> + [{MFA, Contract}|NewContracts] + end; + {error, _Error} -> NewContracts end; - {error, _Error} -> NewContracts + error -> NewContracts end; error -> NewContracts end diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl index f353638cdf..4436330f7f 100644 --- a/lib/dialyzer/src/dialyzer_gui.erl +++ b/lib/dialyzer/src/dialyzer_gui.erl @@ -88,8 +88,8 @@ -spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS. -start(DialyzerOptions = #options{from = From, init_plt = InitPltFile, - legal_warnings = LegalWarnings}) -> +start(#options{from = From, init_plts = InitPltFiles, + legal_warnings = LegalWarnings} = DialyzerOptions) -> process_flag(trap_exit, true), GS = gs:start(), @@ -336,9 +336,13 @@ start(DialyzerOptions = #options{from = From, init_plt = InitPltFile, gs:config(Packer, WH), {ok, CWD} = file:get_cwd(), - InitPlt = try dialyzer_plt:from_file(InitPltFile) - catch throw:{dialyzer_error, _} -> dialyzer_plt:new() - end, + InitPlt = + case InitPltFiles of + [] -> dialyzer_plt:new(); + _ -> + Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles], + dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts) + end, State = #gui_state{add_all = AddAll, add_file = AddFile, diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index 2e309d7ec1..e711c15ea7 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -88,7 +88,7 @@ start(DialyzerOptions) -> State = wx:batch(fun() -> create_window(Wx, DialyzerOptions) end), gui_loop(State). -create_window(Wx, DialyzerOptions) -> +create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) -> {ok, Host} = inet:gethostname(), %%---------- initializing frame --------- @@ -258,11 +258,15 @@ create_window(Wx, DialyzerOptions) -> plt = PltMenu, options =OptionsMenu, help = HelpMenu}, - - InitPlt = try dialyzer_plt:from_file(DialyzerOptions#options.init_plt) - catch throw:{dialyzer_error, _} -> dialyzer_plt:new() - end, + InitPlt = + case InitPltFiles of + [] -> dialyzer_plt:new(); + _ -> + Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles], + dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts) + end, + #gui_state{add = AddButton, add_dir = AddDirButton, add_rec = AddRecButton, diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 010625b7bd..2c0afa6e2b 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -53,14 +53,21 @@ build(Opts) -> InitPlt = dialyzer_plt:get_default_plt(), DefaultOpts = #options{}, DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns1, - init_plt = InitPlt}, - try - NewOpts = build_options(Opts, DefaultOpts1), + init_plts = [InitPlt]}, + try + Opts1 = preprocess_opts(Opts), + NewOpts = build_options(Opts1, DefaultOpts1), postprocess_opts(NewOpts) catch throw:{dialyzer_options_error, Msg} -> {error, Msg} end. +preprocess_opts([]) -> []; +preprocess_opts([{init_plt, File}|Opts]) -> + [{plts, [File]}|preprocess_opts(Opts)]; +preprocess_opts([Opt|Opts]) -> + [Opt|preprocess_opts(Opts)]. + postprocess_opts(Opts = #options{}) -> Opts1 = check_output_plt(Opts), adapt_get_warnings(Opts1). @@ -144,9 +151,9 @@ build_options([{OptionName, Value} = Term|Rest], Options) -> build_options(Rest, Options#options{from = Value}); get_warnings -> build_options(Rest, Options#options{get_warnings = Value}); - init_plt -> - assert_filenames([Term], [Value]), - build_options(Rest, Options#options{init_plt = Value}); + plts -> + assert_filenames(Term, Value), + build_options(Rest, Options#options{init_plts = Value}); include_dirs -> assert_filenames(Term, Value), OldVal = Options#options.include_dirs, diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index 08d0b318b5..a7ba270c41 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -51,6 +51,7 @@ lookup_contract/2, lookup_module/2, merge_plts/1, + merge_plts_or_report_conflicts/2, new/0, plt_and_info_from_file/1, get_specs/1, @@ -292,6 +293,38 @@ merge_plts(List) -> exported_types = sets_merge(ExpTypesList), contracts = table_merge(ContractsList)}. +-spec merge_disj_plts([plt()]) -> plt(). + +merge_disj_plts(List) -> + InfoList = [Info || #plt{info = Info} <- List], + TypesList = [Types || #plt{types = Types} <- List], + ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List], + ContractsList = [Contracts || #plt{contracts = Contracts} <- List], + #plt{info = table_disj_merge(InfoList), + types = table_disj_merge(TypesList), + exported_types = sets_disj_merge(ExpTypesList), + contracts = table_disj_merge(ContractsList)}. + +-spec merge_plts_or_report_conflicts([file:filename()], [plt()]) -> plt(). + +merge_plts_or_report_conflicts(PltFiles, Plts) -> + try + merge_disj_plts(Plts) + catch throw:{dialyzer_error, not_disjoint_plts} -> + IncFiles = lists:append([begin {ok, Fs} = included_files(F), Fs end + || F <- PltFiles]), + ConfFiles = find_duplicates(IncFiles), + Msg = io_lib:format("Could not merge PLTs since they are not disjoint\n" + "The following files are included in more than one " + "PLTs:\n~p\n", [ConfFiles]), + error(Msg) + end. + +find_duplicates(List) -> + ModList = [filename:basename(E) || E <- List], + SortedList = lists:usort(ModList), + lists:usort(ModList -- SortedList). + -spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'. to_file(FileName, @@ -556,6 +589,25 @@ table_merge([Plt|Plts], Acc) -> NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc), table_merge(Plts, NewAcc). +table_disj_merge([H|T]) -> + table_disj_merge(T, H). + +table_disj_merge([], Acc) -> + Acc; +table_disj_merge([Plt|Plts], Acc) -> + case table_is_disjoint(Plt, Acc) of + true -> + NewAcc = dict:merge(fun(_Key, _Val1, _Val2) -> gazonk end, + Plt, Acc), + table_disj_merge(Plts, NewAcc); + false -> throw({dialyzer_error, not_disjoint_plts}) + end. + +table_is_disjoint(T1, T2) -> + K1 = dict:fetch_keys(T1), + K2 = dict:fetch_keys(T2), + lists:all(fun(E) -> not lists:member(E, K2) end, K1). + sets_merge([H|T]) -> sets_merge(T, H). @@ -565,6 +617,19 @@ sets_merge([Plt|Plts], Acc) -> NewAcc = sets:union(Plt, Acc), sets_merge(Plts, NewAcc). +sets_disj_merge([H|T]) -> + sets_disj_merge(T, H). + +sets_disj_merge([], Acc) -> + Acc; +sets_disj_merge([Plt|Plts], Acc) -> + case sets:is_disjoint(Plt, Acc) of + true -> + NewAcc = sets:union(Plt, Acc), + sets_disj_merge(Plts, NewAcc); + false -> throw({dialyzer_error, not_disjoint_plts}) + end. + %%--------------------------------------------------------------------------- %% Debug utilities. diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 3effb1c2e6..f68472d2fc 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -314,6 +314,7 @@ traverse(Tree, DefinedVars, State) -> error -> t_fun(length(Vars), t_none()); {ok, Dom} -> t_fun(Dom, t_none()) end, + TreeVar = mk_var(Tree), State2 = try State1 = case state__add_prop_constrs(Tree, State0) of @@ -321,20 +322,21 @@ traverse(Tree, DefinedVars, State) -> PropState -> PropState end, {BodyState, BodyVar} = traverse(Body, DefinedVars1, State1), - state__store_conj(mk_var(Tree), eq, + state__store_conj(TreeVar, eq, t_fun(mk_var_list(Vars), BodyVar), BodyState) catch throw:error -> - state__store_conj(mk_var(Tree), eq, FunFailType, State0) + state__store_conj(TreeVar, eq, FunFailType, State0) end, Cs = state__cs(State2), - State3 = state__store_constrs(mk_var(Tree), Cs, State2), - Ref = mk_constraint_ref(mk_var(Tree), get_deps(Cs)), + State3 = state__store_constrs(TreeVar, Cs, State2), + Ref = mk_constraint_ref(TreeVar, get_deps(Cs)), OldCs = state__cs(State), State4 = state__new_constraint_context(State3), State5 = state__store_conj_list([OldCs, Ref], State4), State6 = state__store_fun_arity(Tree, State5), - {State6, mk_var(Tree)}; + State7 = state__add_fun_to_scc(TreeVar, State6), + {State7, TreeVar}; 'let' -> Vars = cerl:let_vars(Tree), Arg = cerl:let_arg(Tree), @@ -580,7 +582,7 @@ handle_try(Tree, DefinedVars, State) -> mk_conj_constraint_list([HandlerCs, mk_constraint(TreeVar, eq, HandlerVar)]), Disj = mk_disj_constraint_list([Conj1, Conj2]), - {Disj, mk_var(Tree)}; + {Disj, TreeVar}; {false, true} -> {mk_conj_constraint_list([ArgBodyCs, mk_constraint(TreeVar, eq, BodyVar)]), @@ -2070,7 +2072,7 @@ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes) -> NameMap = dict:from_list([{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0]), SCC = [mk_var(Fun) || {_MFA, {_Var, Fun}, _Rec} <- SCC0], #state{callgraph = CallGraph, name_map = NameMap, next_label = NextLabel, - prop_types = PropTypes, plt = Plt, scc = SCC}. + prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC)}. state__set_rec_dict(State, RecDict) -> State#state{records = RecDict}. @@ -2161,6 +2163,9 @@ get_apply_constr(FunLabels, Dst, ArgTypes, #state{callgraph = CG} = State) -> state__scc(#state{scc = SCC}) -> SCC. +state__add_fun_to_scc(Fun, #state{scc = SCC} = State) -> + State#state{scc = ordsets:add_element(Fun, SCC)}. + state__plt(#state{plt = PLT}) -> PLT. |