diff options
Diffstat (limited to 'lib/dialyzer/src')
27 files changed, 2997 insertions, 1508 deletions
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile index 256f20f549..fc08e7ca2f 100644 --- a/lib/dialyzer/src/Makefile +++ b/lib/dialyzer/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2006-2016. All Rights Reserved. +# Copyright Ericsson AB 2006-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,7 +68,8 @@ MODULES = \ dialyzer_typesig \ dialyzer_coordinator \ dialyzer_worker \ - dialyzer_utils + dialyzer_utils \ + typer HRL_FILES= dialyzer.hrl dialyzer_gui_wx.hrl ERL_FILES= $(MODULES:%=%.erl) @@ -117,6 +118,9 @@ $(EBIN)/dialyzer_plt.$(EMULATOR): dialyzer_plt.erl ../vsn.mk $(EBIN)/dialyzer_gui_wx.$(EMULATOR): dialyzer_gui_wx.erl ../vsn.mk $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui_wx.erl +$(EBIN)/typer.$(EMULATOR): typer.erl ../vsn.mk + $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) typer.erl + $(APP_TARGET): $(APP_SRC) ../vsn.mk $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index 5b28f7ae86..e3a0fc967d 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -2,7 +2,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -43,10 +43,11 @@ dialyzer_typesig, dialyzer_utils, dialyzer_timing, - dialyzer_worker]}, + dialyzer_worker, + typer]}, {registered, []}, {applications, [compiler, hipe, kernel, stdlib, wx]}, {env, []}, - {runtime_dependencies, ["wx-1.2","syntax_tools-2.0","stdlib-3.0", - "kernel-5.0","hipe-3.15.1","erts-8.0", + {runtime_dependencies, ["wx-1.2","syntax_tools-2.0","stdlib-3.4", + "kernel-5.3","hipe-3.16.1","erts-9.0", "compiler-7.0"]}]}. diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index bcac8afe64..1538174d4a 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer.erl @@ -118,13 +111,13 @@ 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\n", + io_lib:format("The PLT ~ts includes the following files:\n~tp\n\n", [PLT, Files]); {error, read_error} -> - Msg = io_lib:format("Could not read the PLT file ~p\n\n", [PLT]), + Msg = io_lib:format("Could not read the PLT file ~tp\n\n", [PLT]), throw({dialyzer_error, Msg}); {error, no_such_file} -> - Msg = io_lib:format("The PLT file ~p does not exist\n\n", [PLT]), + Msg = io_lib:format("The PLT file ~tp does not exist\n\n", [PLT]), throw({dialyzer_error, Msg}) end, String ++ get_plt_info(PLTs); @@ -133,16 +126,16 @@ get_plt_info([]) -> "". do_print_plt_info(PLTInfo, OutputFile) -> case OutputFile =:= none of true -> - io:format("~s", [PLTInfo]), + io:format("~ts", [PLTInfo]), ?RET_NOTHING_SUSPICIOUS; false -> case file:open(OutputFile, [write]) of {ok, FileDesc} -> - io:format(FileDesc, "~s", [PLTInfo]), + io:format(FileDesc, "~ts", [PLTInfo]), ok = file:close(FileDesc), ?RET_NOTHING_SUSPICIOUS; {error, Reason} -> - Msg1 = io_lib:format("Could not open output file ~p, Reason: ~p\n", + Msg1 = io_lib:format("Could not open output file ~tp, Reason: ~p\n", [OutputFile, Reason]), throw({dialyzer_error, Msg1}) end @@ -271,7 +264,7 @@ cl_halt({ok, R = ?RET_DISCREPANCIES}, #options{output_file = Output}) -> halt(R); cl_halt({error, Msg1}, #options{output_file = Output}) -> %% Msg2 = "dialyzer: Internal problems were encountered in the analysis", - io:format("\ndialyzer: ~s\n", [Msg1]), + io:format("\ndialyzer: ~ts\n", [Msg1]), cl_check_log(Output), halt(?RET_INTERNAL_ERROR). @@ -280,9 +273,9 @@ cl_halt({error, Msg1}, #options{output_file = Output}) -> cl_check_log(none) -> ok; cl_check_log(Output) -> - io:format(" Check output file `~s' for details\n", [Output]). + io:format(" Check output file `~ts' for details\n", [Output]). --spec format_warning(raw_warning()) -> string(). +-spec format_warning(raw_warning() | dial_warning()) -> string(). format_warning(W) -> format_warning(W, basename). @@ -298,7 +291,7 @@ format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File), basename -> filename:basename(File) end, String = lists:flatten(message_to_string(Msg)), - lists:flatten(io_lib:format("~s:~w: ~s", [F, Line, String])). + lists:flatten(io_lib:format("~ts:~w: ~ts", [F, Line, String])). %%----------------------------------------------------------------------------- @@ -309,54 +302,55 @@ format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File), %%----- Warnings for general discrepancies ---------------- message_to_string({apply, [Args, ArgNs, FailReason, SigArgs, SigRet, Contract]}) -> - io_lib:format("Fun application with arguments ~s ", [Args]) ++ + io_lib:format("Fun application with arguments ~ts ", [Args]) ++ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract); message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) -> - io_lib:format("The call ~s:~s~s requires that ~s is of type ~s not ~s\n", + io_lib:format("The call ~s:~ts~ts requires that ~ts is of type ~ts not ~ts\n", [M, F, Args, Culprit, ExpectedType, FoundType]); message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) -> io_lib:format("Binary construction will fail since the ~s field ~s in" " segment ~s has type ~s\n", [Culprit, Size, Seg, Type]); message_to_string({call, [M, F, Args, ArgNs, FailReason, SigArgs, SigRet, Contract]}) -> - io_lib:format("The call ~w:~w~s ", [M, F, Args]) ++ + io_lib:format("The call ~w:~tw~ts ", [M, F, Args]) ++ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract); message_to_string({call_to_missing, [M, F, A]}) -> - io_lib:format("Call to missing or unexported function ~w:~w/~w\n", [M, F, A]); + io_lib:format("Call to missing or unexported function ~w:~tw/~w\n", + [M, F, A]); message_to_string({exact_eq, [Type1, Op, Type2]}) -> - io_lib:format("The test ~s ~s ~s can never evaluate to 'true'\n", + io_lib:format("The test ~ts ~s ~ts can never evaluate to 'true'\n", [Type1, Op, Type2]); message_to_string({fun_app_args, [Args, Type]}) -> - io_lib:format("Fun application with arguments ~s will fail" - " since the function has type ~s\n", [Args, Type]); + io_lib:format("Fun application with arguments ~ts will fail" + " since the function has type ~ts\n", [Args, Type]); message_to_string({fun_app_no_fun, [Op, Type, Arity]}) -> - io_lib:format("Fun application will fail since ~s :: ~s" + io_lib:format("Fun application will fail since ~ts :: ~ts" " is not a function of arity ~w\n", [Op, Type, Arity]); message_to_string({guard_fail, []}) -> "Clause guard cannot succeed.\n"; message_to_string({guard_fail, [Arg1, Infix, Arg2]}) -> - io_lib:format("Guard test ~s ~s ~s can never succeed\n", [Arg1, Infix, Arg2]); + io_lib:format("Guard test ~ts ~s ~ts can never succeed\n", [Arg1, Infix, Arg2]); message_to_string({map_update, [Type, Key]}) -> - io_lib:format("A key of type ~s cannot exist " - "in a map of type ~s\n", [Key, Type]); + io_lib:format("A key of type ~ts cannot exist " + "in a map of type ~ts\n", [Key, Type]); message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) -> - io_lib:format("Guard test not(~s ~s ~s) can never succeed\n", + io_lib:format("Guard test not(~ts ~s ~ts) can never succeed\n", [Arg1, Infix, Arg2]); message_to_string({guard_fail, [Guard, Args]}) -> - io_lib:format("Guard test ~w~s can never succeed\n", [Guard, Args]); + io_lib:format("Guard test ~w~ts can never succeed\n", [Guard, Args]); message_to_string({neg_guard_fail, [Guard, Args]}) -> - io_lib:format("Guard test not(~w~s) can never succeed\n", [Guard, Args]); + io_lib:format("Guard test not(~w~ts) can never succeed\n", [Guard, Args]); message_to_string({guard_fail_pat, [Pat, Type]}) -> - io_lib:format("Clause guard cannot succeed. The ~s was matched" - " against the type ~s\n", [Pat, Type]); + io_lib:format("Clause guard cannot succeed. The ~ts was matched" + " against the type ~ts\n", [Pat, Type]); message_to_string({improper_list_constr, [TlType]}) -> io_lib:format("Cons will produce an improper list" - " since its 2nd argument is ~s\n", [TlType]); + " since its 2nd argument is ~ts\n", [TlType]); message_to_string({no_return, [Type|Name]}) -> NameString = case Name of [] -> "The created fun "; - [F, A] -> io_lib:format("Function ~w/~w ", [F, A]) + [F, A] -> io_lib:format("Function ~tw/~w ", [F, A]) end, case Type of no_match -> NameString ++ "has no clauses that will ever match\n"; @@ -365,125 +359,129 @@ message_to_string({no_return, [Type|Name]}) -> both -> NameString ++ "has no local return\n" end; message_to_string({record_constr, [RecConstr, FieldDiffs]}) -> - io_lib:format("Record construction ~s violates the" - " declared type of field ~s\n", [RecConstr, FieldDiffs]); + io_lib:format("Record construction ~ts violates the" + " declared type of field ~ts\n", [RecConstr, FieldDiffs]); message_to_string({record_constr, [Name, Field, Type]}) -> - io_lib:format("Record construction violates the declared type for #~w{}" - " since ~s cannot be of type ~s\n", [Name, Field, Type]); + io_lib:format("Record construction violates the declared type for #~tw{}" + " since ~ts cannot be of type ~ts\n", [Name, Field, Type]); message_to_string({record_matching, [String, Name]}) -> - io_lib:format("The ~s violates the" - " declared type for #~w{}\n", [String, Name]); + io_lib:format("The ~ts violates the" + " declared type for #~tw{}\n", [String, Name]); message_to_string({record_match, [Pat, Type]}) -> - io_lib:format("Matching of ~s tagged with a record name violates the declared" - " type of ~s\n", [Pat, Type]); + io_lib:format("Matching of ~ts tagged with a record name violates" + " the declared type of ~ts\n", [Pat, Type]); message_to_string({pattern_match, [Pat, Type]}) -> - io_lib:format("The ~s can never match the type ~s\n", [Pat, Type]); + io_lib:format("The ~ts can never match the type ~ts\n", [Pat, Type]); message_to_string({pattern_match_cov, [Pat, Type]}) -> - io_lib:format("The ~s can never match since previous" - " clauses completely covered the type ~s\n", + io_lib:format("The ~ts can never match since previous" + " clauses completely covered the type ~ts\n", [Pat, Type]); message_to_string({unmatched_return, [Type]}) -> - io_lib:format("Expression produces a value of type ~s," + io_lib:format("Expression produces a value of type ~ts," " but this value is unmatched\n", [Type]); message_to_string({unused_fun, [F, A]}) -> - io_lib:format("Function ~w/~w will never be called\n", [F, A]); + io_lib:format("Function ~tw/~w will never be called\n", [F, A]); %%----- Warnings for specs and contracts ------------------- message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~w~s" - " is not equal to the success typing: ~w:~w~s\n", + io_lib:format("Type specification ~w:~tw~ts" + " is not equal to the success typing: ~w:~tw~ts\n", [M, F, Contract, M, F, Sig]); message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~w~s" - " is a subtype of the success typing: ~w:~w~s\n", + io_lib:format("Type specification ~w:~tw~ts" + " is a subtype of the success typing: ~w:~tw~ts\n", [M, F, Contract, M, F, Sig]); message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~w~s" - " is a supertype of the success typing: ~w:~w~s\n", + io_lib:format("Type specification ~w:~tw~ts" + " is a supertype of the success typing: ~w:~tw~ts\n", [M, F, Contract, M, F, Sig]); message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}) -> - io_lib:format("The contract ~w:~w~s cannot be right because the inferred" - " return for ~w~s on line ~w is ~s\n", + io_lib:format("The contract ~w:~tw~ts cannot be right because the inferred" + " return for ~tw~ts on line ~w is ~ts\n", [M, F, Contract, F, ArgStrings, Line, CRet]); message_to_string({invalid_contract, [M, F, A, Sig]}) -> - io_lib:format("Invalid type specification for function ~w:~w/~w." - " The success typing is ~s\n", [M, F, A, Sig]); + io_lib:format("Invalid type specification for function ~w:~tw/~w." + " The success typing is ~ts\n", [M, F, A, Sig]); +message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]}) -> + io_lib:format("The specification for ~w:~tw/~w" + " has an opaque subtype ~ts which is violated by the" + " success typing ~ts\n", [M, F, A, OpaqueType, SigType]); message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}) -> - io_lib:format("The specification for ~w:~w/~w states that the function" - " might also return ~s but the inferred return is ~s\n", + io_lib:format("The specification for ~w:~tw/~w states that the function" + " might also return ~ts but the inferred return is ~ts\n", [M, F, A, ExtraRanges, SigRange]); message_to_string({overlapping_contract, [M, F, A]}) -> - io_lib:format("Overloaded contract for ~w:~w/~w has overlapping domains;" + io_lib:format("Overloaded contract for ~w:~tw/~w has overlapping domains;" " such contracts are currently unsupported and are simply ignored\n", [M, F, A]); message_to_string({spec_missing_fun, [M, F, A]}) -> - io_lib:format("Contract for function that does not exist: ~w:~w/~w\n", + io_lib:format("Contract for function that does not exist: ~w:~tw/~w\n", [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 when ~s\n", + io_lib:format("The call ~w:~tw~ts contains ~ts when ~ts\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", + io_lib:format("The call ~w:~tw~ts does not have ~ts\n", [M, F, Args, form_expected_without_opaque(ExpectedTriples)]); message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) -> - io_lib:format("Attempt to test for equality between a term of type ~s" - " and a term of opaque type ~s\n", [Type, OpaqueType]); + io_lib:format("Attempt to test for equality between a term of type ~ts" + " and a term of opaque type ~ts\n", [Type, OpaqueType]); message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) -> - io_lib:format("Guard test ~s ~s ~s contains ~s\n", + io_lib:format("Guard test ~ts ~s ~ts contains ~s\n", [Arg1, Infix, Arg2, form_positions(ArgNs)]); message_to_string({opaque_guard, [Guard, Args]}) -> - io_lib:format("Guard test ~w~s breaks the opaqueness of its argument\n", + io_lib:format("Guard test ~w~ts breaks the opacity of its argument\n", [Guard, Args]); message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) -> Term = if OpaqueType =:= OpaqueTerm -> "the term"; true -> OpaqueTerm end, - io_lib:format("The attempt to match a term of type ~s against the ~s" - " breaks the opaqueness of ~s\n", [OpaqueType, Pat, Term]); + io_lib:format("The attempt to match a term of type ~s against the ~ts" + " breaks the opacity of ~ts\n", [OpaqueType, Pat, Term]); message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) -> - io_lib:format("Attempt to test for inequality between a term of type ~s" - " and a term of opaque type ~s\n", [Type, OpaqueType]); + io_lib:format("Attempt to test for inequality between a term of type ~ts" + " and a term of opaque type ~ts\n", [Type, OpaqueType]); message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) -> - io_lib:format("The type test ~s~s breaks the opaqueness of the term ~s~s\n", + io_lib:format("The type test ~ts~ts breaks the opacity of the term ~ts~ts\n", [Fun, Args, Arg, ArgType]); message_to_string({opaque_size, [SizeType, Size]}) -> - io_lib:format("The size ~s breaks the opaqueness of ~s\n", + io_lib:format("The size ~ts breaks the opacity of ~ts\n", [SizeType, Size]); message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) -> - io_lib:format("The call ~s:~s~s breaks the opaqueness of the term ~s :: ~s\n", + io_lib:format("The call ~s:~ts~ts breaks the opacity of the term ~ts :: ~ts\n", [M, F, Args, Culprit, OpaqueType]); %%----- Warnings for concurrency errors -------------------- message_to_string({race_condition, [M, F, Args, Reason]}) -> - io_lib:format("The call ~w:~w~s ~s\n", [M, F, Args, Reason]); + io_lib:format("The call ~w:~tw~ts ~ts\n", [M, F, Args, Reason]); %%----- Warnings for behaviour errors -------------------- message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}) -> - io_lib:format("The inferred return type of ~w/~w (~s) has nothing in common" - " with ~s, which is the expected return type for the callback of" - " ~w behaviour\n", [F, A, ST, CT, B]); + io_lib:format("The inferred return type of ~tw/~w (~ts) has nothing in" + " common with ~ts, which is the expected return type for" + " the callback of the ~w behaviour\n", [F, A, ST, CT, B]); message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> - io_lib:format("The inferred type for the ~s argument of ~w/~w (~s) is" - " not a supertype of ~s, which is expected type for this" + io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts) is" + " not a supertype of ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", [ordinal(N), F, A, ST, CT, B]); message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}) -> - io_lib:format("The return type ~s in the specification of ~w/~w is not a" - " subtype of ~s, which is the expected return type for the" - " callback of ~w behaviour\n", [ST, F, A, CT, B]); + io_lib:format("The return type ~ts in the specification of ~tw/~w is not a" + " subtype of ~ts, which is the expected return type for the" + " callback of the ~w behaviour\n", [ST, F, A, CT, B]); message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> - io_lib:format("The specified type for the ~s argument of ~w/~w (~s) is" - " not a supertype of ~s, which is expected type for this" + io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) is" + " not a supertype of ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", [ordinal(N), F, A, ST, CT, B]); message_to_string({callback_missing, [B, F, A]}) -> - io_lib:format("Undefined callback function ~w/~w (behaviour '~w')\n", + io_lib:format("Undefined callback function ~tw/~w (behaviour ~w)\n", [F, A, B]); message_to_string({callback_info_missing, [B]}) -> io_lib:format("Callback info about the ~w behaviour is not available\n", [B]); %%----- Warnings for unknown functions, types, and behaviours ------------- message_to_string({unknown_type, {M, F, A}}) -> - io_lib:format("Unknown type ~w:~w/~w", [M, F, A]); + io_lib:format("Unknown type ~w:~tw/~w", [M, F, A]); message_to_string({unknown_function, {M, F, A}}) -> - io_lib:format("Unknown function ~w:~w/~w", [M, F, A]); + io_lib:format("Unknown function ~w:~tw/~w", [M, F, A]); message_to_string({unknown_behaviour, B}) -> io_lib:format("Unknown behaviour ~w", [B]). @@ -500,24 +498,24 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, true -> %% We do not know which argument(s) caused the failure io_lib:format("will never return since the success typing arguments" - " are ~s\n", [SigArgs]); + " are ~ts\n", [SigArgs]); false -> io_lib:format("will never return since it differs in the ~s argument" - " from the success typing arguments: ~s\n", + " from the success typing arguments: ~ts\n", [PositionString, SigArgs]) end; only_contract -> case (ArgNs =:= []) orelse IsOverloaded of true -> %% We do not know which arguments caused the failure - io_lib:format("breaks the contract ~s\n", [Contract]); + io_lib:format("breaks the contract ~ts\n", [Contract]); false -> - io_lib:format("breaks the contract ~s in the ~s argument\n", + io_lib:format("breaks the contract ~ts in the ~s argument\n", [Contract, PositionString]) end; both -> - io_lib:format("will never return since the success typing is ~s -> ~s" - " and the contract is ~s\n", [SigArgs, SigRet, Contract]) + io_lib:format("will never return since the success typing is ~ts -> ~ts" + " and the contract is ~ts\n", [SigArgs, SigRet, Contract]) end. form_positions(ArgNs) -> @@ -535,9 +533,9 @@ form_positions(ArgNs) -> form_expected_without_opaque([{N, T, TStr}]) -> case erl_types:t_is_opaque(T) of true -> - io_lib:format("an opaque term of type ~s as ", [TStr]); + io_lib:format("an opaque term of type ~ts as ", [TStr]); false -> - io_lib:format("a term of type ~s (with opaque subterms) as ", [TStr]) + io_lib:format("a term of type ~ts (with opaque subterms) as ", [TStr]) end ++ form_position_string([N]) ++ " argument"; form_expected_without_opaque(ExpectedTriples) -> %% TODO: can do much better here {ArgNs, _Ts, _TStrs} = lists:unzip3(ExpectedTriples), @@ -548,8 +546,8 @@ form_expected(ExpectedArgs) -> [T] -> TS = erl_types:t_to_string(T), case erl_types:t_is_opaque(T) of - true -> io_lib:format("an opaque term of type ~s is expected", [TS]); - false -> io_lib:format("a structured term of type ~s is expected", [TS]) + true -> io_lib:format("an opaque term of type ~ts is expected", [TS]); + false -> io_lib:format("a structured term of type ~ts is expected", [TS]) end; [_,_|_] -> "terms of different types are expected in these positions" end. diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index ea6a71217c..e7cf2860b7 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -1,9 +1,5 @@ %%% This is an -*- Erlang -*- file. %%% -%%% %CopyrightBegin% -%%% -%%% Copyright Ericsson AB 2006-2015. All Rights Reserved. -%%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at @@ -16,8 +12,6 @@ %%% See the License for the specific language governing permissions and %%% limitations under the License. %%% -%%% %CopyrightEnd% -%%% %%%------------------------------------------------------------------- %%% File : dialyzer.hrl %%% Author : Tobias Lindahl <[email protected]> diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 50fc1d8471..9993c68fed 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%-------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_analysis_callgraph.erl @@ -99,6 +92,7 @@ loop(#server_state{parent = Parent} = State, send_warnings(Parent, Warnings), loop(State, Analysis, ExtCalls); {AnalPid, cserver, CServer, Plt} -> + skip_ets_transfer(AnalPid), send_codeserver_plt(Parent, CServer, Plt), loop(State, Analysis, ExtCalls); {AnalPid, done, Plt, DocPlt} -> @@ -140,79 +134,124 @@ analysis_start(Parent, Analysis, LegalWarnings) -> Files = ordsets:from_list(Analysis#analysis.files), {Callgraph, TmpCServer0} = compile_and_store(Files, State), %% Remote type postprocessing - NewCServer = - try - NewRecords = dialyzer_codeserver:get_temp_records(TmpCServer0), - NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer0), - OldRecords = dialyzer_plt:get_types(Plt), - OldExpTypes0 = dialyzer_plt:get_exported_types(Plt), - MergedRecords = dialyzer_utils:merge_records(NewRecords, OldRecords), - RemMods = - [case Analysis#analysis.start_from of - byte_code -> list_to_atom(filename:basename(F, ".beam")); - src_code -> list_to_atom(filename:basename(F, ".erl")) - end || F <- Files], - OldExpTypes1 = dialyzer_utils:sets_filter(RemMods, OldExpTypes0), - MergedExpTypes = sets:union(NewExpTypes, OldExpTypes1), - TmpCServer1 = dialyzer_codeserver:set_temp_records(MergedRecords, TmpCServer0), - TmpCServer2 = - dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1), - ?timing(State#analysis_state.timing_server, "remote", - begin - TmpCServer3 = - dialyzer_utils:process_record_remote_types(TmpCServer2), - dialyzer_contracts:process_contract_remote_types(TmpCServer3) - end) - catch - throw:{error, _ErrorMsg} = Error -> exit(Error) - end, - NewPlt0 = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)), - ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer), - NewPlt1 = dialyzer_plt:insert_exported_types(NewPlt0, ExpTypes), - State0 = State#analysis_state{plt = NewPlt1}, - dump_callgraph(Callgraph, State0, Analysis), - State1 = State0#analysis_state{codeserver = NewCServer}, + Args = {Plt, Analysis, Parent}, + NewCServer = remote_type_postprocessing(TmpCServer0, Args), + dump_callgraph(Callgraph, State, Analysis), %% Remove all old versions of the files being analyzed AllNodes = dialyzer_callgraph:all_nodes(Callgraph), - Plt1 = dialyzer_plt:delete_list(NewPlt1, AllNodes), + Plt1_a = dialyzer_plt:delete_list(Plt, AllNodes), + Plt1 = dialyzer_plt:insert_callbacks(Plt1_a, NewCServer), + State1 = State#analysis_state{codeserver = NewCServer, plt = Plt1}, Exports = dialyzer_codeserver:get_exports(NewCServer), + NonExports = sets:subtract(sets:from_list(AllNodes), Exports), + NonExportsList = sets:to_list(NonExports), NewCallgraph = case Analysis#analysis.race_detection of true -> dialyzer_callgraph:put_race_detection(true, Callgraph); false -> Callgraph end, - State2 = analyze_callgraph(NewCallgraph, State1#analysis_state{plt = Plt1}), + State2 = analyze_callgraph(NewCallgraph, State1), + #analysis_state{plt = Plt2, + doc_plt = DocPlt, + codeserver = Codeserver0} = State2, + {Codeserver, Plt3} = move_data(Codeserver0, Plt2), dialyzer_callgraph:dispose_race_server(NewCallgraph), - rcv_and_send_ext_types(Parent), - NonExports = sets:subtract(sets:from_list(AllNodes), Exports), - NonExportsList = sets:to_list(NonExports), - Plt2 = dialyzer_plt:delete_list(State2#analysis_state.plt, NonExportsList), - send_codeserver_plt(Parent, CServer, State2#analysis_state.plt), - send_analysis_done(Parent, Plt2, State2#analysis_state.doc_plt). + %% Since the PLT is never used, a dummy is sent: + DummyPlt = dialyzer_plt:new(), + send_codeserver_plt(Parent, Codeserver, DummyPlt), + dialyzer_plt:delete(DummyPlt), + Plt4 = dialyzer_plt:delete_list(Plt3, NonExportsList), + send_analysis_done(Parent, Plt4, DocPlt). + +remote_type_postprocessing(TmpCServer, Args) -> + Fun = fun() -> + exit(try remote_type_postproc(TmpCServer, Args) of + R -> R + catch + throw:{error,_}=Error -> Error + end) + end, + {Pid, Ref} = erlang:spawn_monitor(Fun), + dialyzer_codeserver:give_away(TmpCServer, Pid), + Pid ! {self(), go}, + receive {'DOWN', Ref, process, Pid, Return} -> + skip_ets_transfer(Pid), + case Return of + {error, _ErrorMsg} = Error -> exit(Error); + _ -> Return + end + end. + +remote_type_postproc(TmpCServer0, Args) -> + {Plt, Analysis, Parent} = Args, + fun() -> + Caller = receive {Pid, go} -> Pid end, + TmpCServer1 = dialyzer_utils:merge_types(TmpCServer0, Plt), + NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer0), + OldExpTypes0 = dialyzer_plt:get_exported_types(Plt), + #analysis{start_from = StartFrom, + timing_server = TimingServer} = Analysis, + Files = ordsets:from_list(Analysis#analysis.files), + RemMods = + [case StartFrom of + byte_code -> list_to_atom(filename:basename(F, ".beam")); + src_code -> list_to_atom(filename:basename(F, ".erl")) + end || F <- Files], + OldExpTypes1 = dialyzer_utils:sets_filter(RemMods, OldExpTypes0), + MergedExpTypes = sets:union(NewExpTypes, OldExpTypes1), + TmpCServer2 = + dialyzer_codeserver:finalize_exported_types(MergedExpTypes, + TmpCServer1), + TmpServer4 = + ?timing + (TimingServer, "remote", + begin + TmpCServer3 = + dialyzer_utils:process_record_remote_types(TmpCServer2), + dialyzer_contracts:process_contract_remote_types(TmpCServer3) + end), + rcv_and_send_ext_types(Caller, Parent), + dialyzer_codeserver:give_away(TmpServer4, Caller), + TmpServer4 + end(). + +skip_ets_transfer(Pid) -> + receive + {'ETS-TRANSFER', _Tid, Pid, _HeriData} -> + skip_ets_transfer(Pid) + after 0 -> + ok + end. + +move_data(CServer, Plt) -> + {CServer1, Records} = dialyzer_codeserver:extract_records(CServer), + Plt1 = dialyzer_plt:insert_types(Plt, Records), + {NewCServer, ExpTypes} = dialyzer_codeserver:extract_exported_types(CServer1), + NewPlt = dialyzer_plt:insert_exported_types(Plt1, ExpTypes), + {NewCServer, NewPlt}. analyze_callgraph(Callgraph, #analysis_state{codeserver = Codeserver, doc_plt = DocPlt, + plt = Plt, timing_server = TimingServer, parent = Parent, solvers = Solvers} = State) -> - Plt = dialyzer_plt:insert_callbacks(State#analysis_state.plt, Codeserver), - {NewPlt, NewDocPlt} = - case State#analysis_state.analysis_type of - plt_build -> - NewPlt0 = - dialyzer_succ_typings:analyze_callgraph(Callgraph, Plt, Codeserver, - TimingServer, Solvers, Parent), - {NewPlt0, DocPlt}; - succ_typings -> - {Warnings, NewPlt0, NewDocPlt0} = - dialyzer_succ_typings:get_warnings(Callgraph, Plt, DocPlt, Codeserver, - TimingServer, Solvers, Parent), - Warnings1 = filter_warnings(Warnings, Codeserver), - send_warnings(State#analysis_state.parent, Warnings1), - {NewPlt0, NewDocPlt0} - end, - dialyzer_callgraph:delete(Callgraph), - State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt}. + case State#analysis_state.analysis_type of + plt_build -> + NewPlt = + dialyzer_succ_typings:analyze_callgraph(Callgraph, Plt, Codeserver, + TimingServer, Solvers, Parent), + dialyzer_callgraph:delete(Callgraph), + State#analysis_state{plt = NewPlt, doc_plt = DocPlt}; + succ_typings -> + {Warnings, NewPlt, NewDocPlt} = + dialyzer_succ_typings:get_warnings(Callgraph, Plt, DocPlt, Codeserver, + TimingServer, Solvers, Parent), + dialyzer_callgraph:delete(Callgraph), + Warnings1 = filter_warnings(Warnings, Codeserver), + send_warnings(State#analysis_state.parent, Warnings1), + State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} + end. %%-------------------------------------------------------------------- %% Build the callgraph and fill the codeserver. @@ -269,7 +308,7 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, dict:new(), Files), check_for_duplicate_modules(ModDict); false -> - Msg = io_lib:format("Could not scan the following file(s):~n~s", + Msg = io_lib:format("Could not scan the following file(s):~n~ts", [[Reason || {_Filename, Reason} <- Failed]]), exit({error, Msg}) end, @@ -367,64 +406,46 @@ compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts, DefaultIncludes = default_includes(filename:dirname(File)), SrcCompOpts = dialyzer_utils:src_compiler_opts(), CompOpts = SrcCompOpts ++ Includes ++ Defines ++ DefaultIncludes, - case dialyzer_utils:get_abstract_code_from_src(File, CompOpts) of + case dialyzer_utils:get_core_from_src(File, CompOpts) of {error, _Msg} = Error -> Error; - {ok, AbstrCode} -> - compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, - UseContracts, LegalWarnings) + {ok, Core} -> + compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings) end. compile_byte(File, Callgraph, CServer, UseContracts, LegalWarnings) -> - case dialyzer_utils:get_abstract_code_from_beam(File) of - error -> - {error, " Could not get abstract code for: " ++ File ++ "\n" ++ - " Recompile with +debug_info or analyze starting from source code"}; - {ok, AbstrCode} -> - compile_byte(File, AbstrCode, Callgraph, CServer, UseContracts, - LegalWarnings) - end. - -compile_byte(File, AbstrCode, Callgraph, CServer, UseContracts, - LegalWarnings) -> - case dialyzer_utils:get_compile_options_from_beam(File) of - error -> - {error, " Could not get compile options for: " ++ File ++ "\n" ++ - " Recompile or analyze starting from source code"}; - {ok, CompOpts} -> - compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, - UseContracts, LegalWarnings) + case dialyzer_utils:get_core_from_beam(File) of + {error, _} = Error -> Error; + {ok, Core} -> + compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings) end. -compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, - UseContracts, LegalWarnings) -> - case dialyzer_utils:get_core_from_abstract_code(AbstrCode, CompOpts) of - error -> {error, " Could not get core Erlang code for: " ++ File}; - {ok, Core} -> - Mod = cerl:concrete(cerl:module_name(Core)), - case dialyzer_utils:get_record_and_type_info(AbstrCode) of +compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings) -> + Mod = cerl:concrete(cerl:module_name(Core)), + case dialyzer_utils:get_record_and_type_info(Core) of + {error, _} = Error -> Error; + {ok, RecInfo} -> + CServer1 = + dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), + case dialyzer_utils:get_fun_meta_info(Mod, Core, LegalWarnings) of {error, _} = Error -> Error; - {ok, RecInfo} -> - CServer1 = - dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer), - MetaFunInfo = - dialyzer_utils:get_fun_meta_info(Mod, AbstrCode, LegalWarnings), - CServer2 = - dialyzer_codeserver:insert_fun_meta_info(MetaFunInfo, CServer1), + MetaFunInfo -> + CServer2 = + dialyzer_codeserver:insert_fun_meta_info(MetaFunInfo, CServer1), case UseContracts of true -> - case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of - {error, _} = Error -> Error; - {ok, SpecInfo, CallbackInfo} -> - CServer3 = - dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo, - CallbackInfo, - CServer2), - store_core(Mod, Core, Callgraph, CServer3) + case dialyzer_utils:get_spec_info(Mod, Core, RecInfo) of + {error, _} = Error -> Error; + {ok, SpecInfo, CallbackInfo} -> + CServer3 = + dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo, + CallbackInfo, + CServer2), + store_core(Mod, Core, Callgraph, CServer3) end; false -> store_core(Mod, Core, Callgraph, CServer2) - end end + end end. store_core(Mod, Core, Callgraph, CServer) -> @@ -525,13 +546,13 @@ default_includes(Dir) -> %% Handle Messages %%------------------------------------------------------------------- -rcv_and_send_ext_types(Parent) -> +rcv_and_send_ext_types(SendTo, Parent) -> Self = self(), Self ! {Self, done}, case rcv_ext_types(Self, []) of [] -> ok; ExtTypes -> - Parent ! {Self, ext_types, ExtTypes}, + Parent ! {SendTo, ext_types, ExtTypes}, ok end. @@ -579,7 +600,8 @@ send_ext_types(Parent, ExtTypes) -> Parent ! {self(), ext_types, ExtTypes}, ok. -send_codeserver_plt(Parent, CServer, Plt ) -> +send_codeserver_plt(Parent, CServer, Plt) -> + ok = dialyzer_codeserver:give_away(CServer, Parent), Parent ! {self(), cserver, CServer, Plt}, ok. @@ -598,14 +620,14 @@ format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc) format_bad_calls([{FromMFA, {M, F, A} = To}|Left], CodeServer, Acc) -> {_Var, FunCode} = dialyzer_codeserver:lookup_mfa_code(FromMFA, CodeServer), Msg = {call_to_missing, [M, F, A]}, - {File, Line} = find_call_file_and_line(FunCode, To), + {File, Line} = find_call_file_and_line(FromMFA, FunCode, To, CodeServer), WarningInfo = {File, Line, FromMFA}, NewAcc = [{?WARN_CALLGRAPH, WarningInfo, Msg}|Acc], format_bad_calls(Left, CodeServer, NewAcc); format_bad_calls([], _CodeServer, Acc) -> Acc. -find_call_file_and_line(Tree, MFA) -> +find_call_file_and_line({Module, _, _}, Tree, MFA, CodeServer) -> Fun = fun(SubTree, Acc) -> case cerl:is_c_call(SubTree) of @@ -618,7 +640,7 @@ find_call_file_and_line(Tree, MFA) -> case {cerl:concrete(M), cerl:concrete(F), A} of MFA -> Ann = cerl:get_ann(SubTree), - [{get_file(Ann), get_line(Ann)}|Acc]; + [{get_file(CodeServer, Module, Ann), get_line(Ann)}|Acc]; {erlang, make_fun, 3} -> [CA1, CA2, CA3] = cerl:call_args(SubTree), case @@ -634,7 +656,8 @@ find_call_file_and_line(Tree, MFA) -> of MFA -> Ann = cerl:get_ann(SubTree), - [{get_file(Ann), get_line(Ann)}|Acc]; + [{get_file(CodeServer, Module, Ann), + get_line(Ann)}|Acc]; _ -> Acc end; @@ -654,8 +677,10 @@ get_line([Line|_]) when is_integer(Line) -> Line; get_line([_|Tail]) -> get_line(Tail); get_line([]) -> -1. -get_file([{file, File}|_]) -> File; -get_file([_|Tail]) -> get_file(Tail). +get_file(Codeserver, Module, [{file, FakeFile}|_]) -> + dialyzer_codeserver:translate_fake_file(Codeserver, Module, FakeFile); +get_file(Codeserver, Module, [_|Tail]) -> + get_file(Codeserver, Module, Tail). -spec dump_callgraph(dialyzer_callgraph:callgraph(), #analysis_state{}, #analysis{}) -> 'ok'. @@ -678,12 +703,13 @@ dump_callgraph(CallGraph, _State, #analysis{callgraph_file = File}, ".ps") -> Args = "-Gratio=compress -Gsize=\"100,100\"", dialyzer_callgraph:to_ps(CallGraph, File, Args); dump_callgraph(CallGraph, State, #analysis{callgraph_file = File}, _Ext) -> + %% TODO: write the graph, not the ETS table identifiers. case file:open(File, [write]) of {ok, Fd} -> io:format(Fd, "~p", [CallGraph]), ok = file:close(Fd); {error, Reason} -> - Msg = io_lib:format("Could not open output file ~p, Reason: ~p\n", + Msg = io_lib:format("Could not open output file ~tp, Reason: ~p\n", [File, Reason]), send_log(State#analysis_state.parent, Msg) end. diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 5623929a43..d380ab2a50 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_behaviours.erl @@ -62,9 +55,9 @@ check_callbacks(Module, Attrs, Records, Plt, Codeserver) -> _ -> MFA = {Module,module_info,0}, {_Var,Code} = dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), - File = get_file(cerl:get_ann(Code)), + File = get_file(Codeserver, Module, cerl:get_ann(Code)), State = #state{plt = Plt, filename = File, behlines = BehLines, - codeserver = Codeserver, records = Records}, + codeserver = Codeserver, records = Records}, Warnings = get_warnings(Module, Behaviours, State), [add_tag_warning_info(Module, W, State) || W <- Warnings] end. @@ -213,12 +206,15 @@ add_tag_warning_info(Module, {_Tag, [_B, Fun, Arity|_R]} = Warn, State) -> dialyzer_codeserver:lookup_mfa_code({Module, Fun, Arity}, State#state.codeserver), Anns = cerl:get_ann(FunCode), - WarningInfo = {get_file(Anns), get_line(Anns), {Module, Fun, Arity}}, + File = get_file(State#state.codeserver, Module, Anns), + WarningInfo = {File, get_line(Anns), {Module, Fun, Arity}}, {?WARN_BEHAVIOUR, WarningInfo, Warn}. get_line([Line|_]) when is_integer(Line) -> Line; get_line([_|Tail]) -> get_line(Tail); get_line([]) -> -1. -get_file([{file, File}|_]) -> File; -get_file([_|Tail]) -> get_file(Tail). +get_file(Codeserver, Module, [{file, FakeFile}|_]) -> + dialyzer_codeserver:translate_fake_file(Codeserver, Module, FakeFile); +get_file(Codeserver, Module, [_|Tail]) -> + get_file(Codeserver, Module, Tail). diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index 50abb22009..7411b1d28b 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_callgraph.erl @@ -47,7 +40,7 @@ module_postorder_from_funs/2, new/0, get_depends_on/2, - get_required_by/2, + %% get_required_by/2, in_neighbours/2, renew_race_info/4, renew_race_code/2, @@ -119,7 +112,11 @@ -opaque callgraph() :: #callgraph{}. --type active_digraph() :: {'d', digraph:graph()} | {'e', ets:tid(), ets:tid()}. +-type active_digraph() :: {'d', digraph:graph()} + | {'e', + Out :: ets:tid(), + In :: ets:tid(), + Map :: ets:tid()}. %%---------------------------------------------------------------------- @@ -248,23 +245,29 @@ find_non_local_calls([], Set) -> -spec get_depends_on(scc() | module(), callgraph()) -> [scc()]. -get_depends_on(SCC, #callgraph{active_digraph = {'e', Out, _In}}) -> - case ets_lookup_dict(SCC, Out) of - {ok, Value} -> Value; - error -> [] - end; +get_depends_on(SCC, #callgraph{active_digraph = {'e', Out, _In, Maps}}) -> + lookup_scc(SCC, Out, Maps); get_depends_on(SCC, #callgraph{active_digraph = {'d', DG}}) -> digraph:out_neighbours(DG, SCC). --spec get_required_by(scc() | module(), callgraph()) -> [scc()]. - -get_required_by(SCC, #callgraph{active_digraph = {'e', _Out, In}}) -> - case ets_lookup_dict(SCC, In) of - {ok, Value} -> Value; +%% -spec get_required_by(scc() | module(), callgraph()) -> [scc()]. + +%% get_required_by(SCC, #callgraph{active_digraph = {'e', _Out, In, Maps}}) -> +%% lookup_scc(SCC, In, Maps); +%% get_required_by(SCC, #callgraph{active_digraph = {'d', DG}}) -> +%% digraph:in_neighbours(DG, SCC). + +lookup_scc(SCC, Table, Maps) -> + case ets_lookup_dict({'scc', SCC}, Maps) of + {ok, SCCInt} -> + case ets_lookup_dict(SCCInt, Table) of + {ok, Ints} -> + [ets:lookup_element(Maps, Int, 2) || Int <- Ints]; + error -> + [] + end; error -> [] - end; -get_required_by(SCC, #callgraph{active_digraph = {'d', DG}}) -> - digraph:in_neighbours(DG, SCC). + end. %%---------------------------------------------------------------------- %% Handling of modules & SCCs @@ -282,9 +285,11 @@ module_postorder(#callgraph{digraph = DG}) -> Nodes = sets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), MDG = digraph:new([acyclic]), digraph_confirm_vertices(sets:to_list(Nodes), MDG), - Foreach = fun({M1,M2}) -> digraph:add_edge(MDG, M1, M2) end, + Foreach = fun({M1,M2}) -> _ = digraph:add_edge(MDG, M1, M2) end, lists:foreach(Foreach, sets:to_list(Edges)), - {digraph_utils:topsort(MDG), {'d', MDG}}. + %% The out-neighbors of a vertex are the vertices called directly. + %% The used vertices are to occur *before* the calling vertex: + {lists:reverse(digraph_utils:topsort(MDG)), {'d', MDG}}. edge_fold({{M1,_,_},{M2,_,_}}, Set) -> case M1 =/= M2 of @@ -302,7 +307,7 @@ module_deps(#callgraph{digraph = DG}) -> Nodes = sets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), MDG = digraph:new(), digraph_confirm_vertices(sets:to_list(Nodes), MDG), - Foreach = fun({M1,M2}) -> digraph:add_edge(MDG, M1, M2) end, + Foreach = fun({M1,M2}) -> check_add_edge(MDG, M1, M2) end, lists:foreach(Foreach, sets:to_list(Edges)), Deps = [{N, ordsets:from_list(digraph:in_neighbours(MDG, N))} || N <- sets:to_list(Nodes)], @@ -360,7 +365,7 @@ ets_lookup_set(Key, Table) -> %% The core tree must be labeled as by cerl_trees:label/1 (or /2). %% The set of labels in the tree must be disjoint from the set of -%% labels already occuring in the callgraph. +%% labels already occurring in the callgraph. -spec scan_core_tree(cerl:c_module(), callgraph()) -> {[mfa_or_funlbl()], [callgraph_edge()]}. @@ -549,9 +554,21 @@ digraph_add_edge(From, To, DG) -> false -> digraph:add_vertex(DG, To); {To, _} -> ok end, - digraph:add_edge(DG, {From, To}, From, To, []), + check_add_edge(DG, {From, To}, From, To, []), ok. +check_add_edge(G, V1, V2) -> + case digraph:add_edge(G, V1, V2) of + {error, Error} -> exit({add_edge, V1, V2, Error}); + _Edge -> ok + end. + +check_add_edge(G, E, V1, V2, L) -> + case digraph:add_edge(G, E, V1, V2, L) of + {error, Error} -> exit({add_edge, E, V1, V2, L, Error}); + _Edge -> ok + end. + digraph_confirm_vertices([MFA|Left], DG) -> digraph:add_vertex(DG, MFA, confirmed), digraph_confirm_vertices(Left, DG); @@ -582,9 +599,10 @@ digraph_delete(DG) -> active_digraph_delete({'d', DG}) -> digraph:delete(DG); -active_digraph_delete({'e', Out, In}) -> +active_digraph_delete({'e', Out, In, Maps}) -> ets:delete(Out), - ets:delete(In). + ets:delete(In), + ets:delete(Maps). digraph_edges(DG) -> digraph:edges(DG). @@ -737,6 +755,7 @@ put_behaviour_api_calls(Calls, -spec to_dot(callgraph(), file:filename()) -> 'ok'. to_dot(#callgraph{digraph = DG, esc = Esc} = CG, File) -> + %% TODO: handle Unicode names. Fun = fun(L) -> case lookup_name(L, CG) of error -> L; @@ -751,44 +770,60 @@ to_dot(#callgraph{digraph = DG, esc = Esc} = CG, File) -> -spec to_ps(callgraph(), file:filename(), string()) -> 'ok'. to_ps(#callgraph{} = CG, File, Args) -> + %% TODO: handle Unicode names. Dot_File = filename:rootname(File) ++ ".dot", to_dot(CG, Dot_File), - Command = io_lib:format("dot -Tps ~s -o ~s ~s", [Args, File, Dot_File]), + Command = io_lib:format("dot -Tps ~ts -o ~ts ~ts", [Args, File, Dot_File]), _ = os:cmd(Command), ok. condensation(G) -> - SCs = digraph_utils:strong_components(G), - V2I = ets:new(condensation_v2i, []), - I2C = ets:new(condensation_i2c, []), - I2I = ets:new(condensation_i2i, [bag]), - CFun = - fun(SC, N) -> - lists:foreach(fun(V) -> true = ets:insert(V2I, {V,N}) end, SC), - true = ets:insert(I2C, {N, SC}), - N + 1 - end, - lists:foldl(CFun, 1, SCs), - Fun1 = - fun({V1, V2}) -> - I1 = ets:lookup_element(V2I, V1, 2), - I2 = ets:lookup_element(V2I, V2, 2), - I1 =:= I2 orelse ets:insert(I2I, {I1, I2}) - end, - lists:foreach(Fun1, digraph:edges(G)), - Fun3 = - fun({I1, I2}, {Out, In}) -> - SC1 = ets:lookup_element(I2C, I1, 2), - SC2 = ets:lookup_element(I2C, I2, 2), - {dict:append(SC1, SC2, Out), dict:append(SC2, SC1, In)} - end, - {OutDict, InDict} = ets:foldl(Fun3, {dict:new(), dict:new()}, I2I), - [OutETS, InETS] = - [ets:new(Name,[{read_concurrency, true}]) || - Name <- [callgraph_deps_out, callgraph_deps_in]], - ets:insert(OutETS, dict:to_list(OutDict)), - ets:insert(InETS, dict:to_list(InDict)), - ets:delete(V2I), - ets:delete(I2C), - ets:delete(I2I), - {{'e', OutETS, InETS}, SCs}. + {Pid, Ref} = erlang:spawn_monitor(do_condensation(G, self())), + receive {'DOWN', Ref, process, Pid, Result} -> + {SCCInts, OutETS, InETS, MapsETS} = Result, + NewSCCs = [ets:lookup_element(MapsETS, SCCInt, 2) || SCCInt <- SCCInts], + {{'e', OutETS, InETS, MapsETS}, NewSCCs} + end. + +-spec do_condensation(digraph:graph(), pid()) -> fun(() -> no_return()). + +do_condensation(G, Parent) -> + fun() -> + [OutETS, InETS, MapsETS] = + [ets:new(Name,[{read_concurrency, true}]) || + Name <- [callgraph_deps_out, callgraph_deps_in, callgraph_scc_map]], + SCCs = digraph_utils:strong_components(G), + %% Assign unique numbers to SCCs: + Ints = lists:seq(1, length(SCCs)), + IntToSCC = lists:zip(Ints, SCCs), + IntScc = sofs:relation(IntToSCC, [{int, scc}]), + %% Create mapping from unique integers to SCCs: + ets:insert(MapsETS, IntToSCC), + %% Subsitute strong components for vertices in edges using the + %% unique numbers: + C2V = sofs:relation([{SC, V} || SC <- SCCs, V <- SC], [{scc, v}]), + I2V = sofs:relative_product(IntScc, C2V), % [{v, int}] + Es = sofs:relation(digraph:edges(G), [{v, v}]), + R1 = sofs:relative_product(I2V, Es), + R2 = sofs:relative_product(I2V, sofs:converse(R1)), + R2Strict = sofs:strict_relation(R2), + %% Create out-neighbours: + Out = sofs:relation_to_family(sofs:converse(R2Strict)), + ets:insert(OutETS, sofs:to_external(Out)), + %% Sort the SCCs topologically: + DG = sofs:family_to_digraph(Out), + lists:foreach(fun(I) -> digraph:add_vertex(DG, I) end, Ints), + SCCInts0 = digraph_utils:topsort(DG), + digraph:delete(DG), + %% The out-neighbors of a vertex are the vertices called directly. + %% The used vertices are to occur *before* the calling vertex: + SCCInts = lists:reverse(SCCInts0), + %% Create in-neighbours: + In = sofs:relation_to_family(R2Strict), + ets:insert(InETS, sofs:to_external(In)), + %% Create mapping from SCCs to unique integers: + ets:insert(MapsETS, lists:zip([{'scc', SCC} || SCC<- SCCs], Ints)), + lists:foreach(fun(E) -> true = ets:give_away(E, Parent, any) + end, [OutETS, InETS, MapsETS]), + exit({SCCInts, OutETS, InETS, MapsETS}) + end. diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index fc56693ea3..0617be6435 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_cl.erl @@ -37,6 +30,8 @@ -record(cl_state, {backend_pid :: pid() | 'undefined', + code_server = none :: 'none' + | dialyzer_codeserver:codeserver(), erlang_mode = false :: boolean(), external_calls = [] :: [mfa()], external_types = [] :: [mfa()], @@ -82,7 +77,7 @@ init_opts_for_build(Opts) -> [] -> 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", + Msg = io_lib:format("Could not build multiple PLT files: ~ts\n", [format_plts(Plts)]), cl_error(Msg) end; @@ -104,7 +99,7 @@ init_opts_for_add(Opts) -> 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", + Msg = io_lib:format("Could not add to multiple PLT files: ~ts\n", [format_plts(Plts)]), cl_error(Msg) end; @@ -170,7 +165,7 @@ init_opts_for_remove(Opts) -> 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", + Msg = io_lib:format("Could not remove from multiple PLT files: ~ts\n", [format_plts(Plts)]), cl_error(Msg) end; @@ -217,19 +212,19 @@ plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) -> do_analysis(AnalFiles, Opts, Plt, {Md5, ModDeps1}) end; {error, no_such_file} -> - Msg = io_lib:format("Could not find the PLT: ~s\n~s", + Msg = io_lib:format("Could not find the PLT: ~ts\n~s", [InitPlt, default_plt_error_msg()]), cl_error(Msg); {error, not_valid} -> - Msg = io_lib:format("The file: ~s is not a valid PLT file\n~s", + Msg = io_lib:format("The file: ~ts is not a valid PLT file\n~s", [InitPlt, default_plt_error_msg()]), cl_error(Msg); {error, read_error} -> - Msg = io_lib:format("Could not read the PLT: ~s\n~s", + Msg = io_lib:format("Could not read the PLT: ~ts\n~s", [InitPlt, default_plt_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", + Msg = io_lib:format("Could not remove the file ~ts from the PLT: ~ts\n", [F, InitPlt]), cl_error(Msg) end. @@ -269,7 +264,7 @@ 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]) + io:format(" Checking whether the PLT ~ts is up-to-date...", [InitPlt]) end. report_old_version(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> @@ -277,7 +272,7 @@ report_old_version(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> quiet -> ok; _ -> io:put_chars(" no\n"), - io:format(" (the PLT ~s was built with an old version of Dialyzer)\n", + io:format(" (the PLT ~ts was built with an old version of Dialyzer)\n", [InitPlt]) end. @@ -305,19 +300,19 @@ report_analysis_start(#options{analysis_type = Type, 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...", + true -> io:format("Adding information to ~ts...", [OutputPlt]); + false -> io:format("Adding information from ~ts to ~ts...", [InitPlt, OutputPlt]) end; plt_build -> - io:format("Creating PLT ~s ...", [OutputPlt]); + io:format("Creating PLT ~ts ...", [OutputPlt]); plt_check -> - io:format("Rebuilding the information in ~s...", [OutputPlt]); + io:format("Rebuilding the information in ~ts...", [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...", + true -> io:format("Removing information from ~ts...", [OutputPlt]); + false -> io:format("Removing information from ~ts to ~ts...", [InitPlt, OutputPlt]) end; succ_typings -> io:format("Proceeding with analysis...") @@ -425,7 +420,7 @@ assert_writable(PltFile) -> case check_if_writable(PltFile) of true -> ok; false -> - Msg = io_lib:format(" The PLT file ~s is not writable", [PltFile]), + Msg = io_lib:format(" The PLT file ~ts is not writable", [PltFile]), cl_error(Msg) end. @@ -601,9 +596,11 @@ init_output(State0, #options{output_file = OutFile, false -> case file:open(OutFile, [write]) of {ok, File} -> + %% Warnings and errors can include Unicode characters. + ok = io:setopts(File, [{encoding, unicode}]), State#cl_state{output = File}; {error, Reason} -> - Msg = io_lib:format("Could not open output file ~p, Reason: ~p\n", + Msg = io_lib:format("Could not open output file ~tp, Reason: ~p\n", [OutFile, Reason]), cl_error(State, lists:flatten(Msg)) end @@ -637,6 +634,9 @@ cl_loop(State, LogCache) -> {BackendPid, warnings, Warnings} -> NewState = store_warnings(State, Warnings), cl_loop(NewState, LogCache); + {BackendPid, cserver, CodeServer, _Plt} -> % Plt is ignored + NewState = State#cl_state{code_server = CodeServer}, + cl_loop(NewState, LogCache); {BackendPid, done, NewPlt, _NewDocPlt} -> return_value(State, NewPlt); {BackendPid, ext_calls, ExtCalls} -> @@ -689,20 +689,30 @@ cl_error(Msg) -> cl_error(State, Msg) -> case State#cl_state.output of standard_io -> ok; - Outfile -> io:format(Outfile, "\n~s\n", [Msg]) + Outfile -> io:format(Outfile, "\n~ts\n", [Msg]) end, maybe_close_output_file(State), throw({dialyzer_error, lists:flatten(Msg)}). -return_value(State = #cl_state{erlang_mode = ErlangMode, +return_value(State = #cl_state{code_server = CodeServer, + erlang_mode = ErlangMode, mod_deps = ModDeps, output_plt = OutputPlt, plt_info = PltInfo, stored_warnings = StoredWarnings}, Plt) -> + %% Just for now: + case CodeServer =:= none of + true -> + ok; + false -> + dialyzer_codeserver:delete(CodeServer) + end, case OutputPlt =:= none of - true -> ok; - false -> dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo) + true -> + dialyzer_plt:delete(Plt); + false -> + dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo) end, UnknownWarnings = unknown_warnings(State), RetValue = @@ -765,7 +775,7 @@ print_ext_calls(#cl_state{output = Output, end. do_print_ext_calls(Output, [{M,F,A}|T], Before) -> - io:format(Output, "~s~p:~p/~p\n", [Before,M,F,A]), + io:format(Output, "~s~tp:~tp/~p\n", [Before,M,F,A]), do_print_ext_calls(Output, T, Before); do_print_ext_calls(_, [], _) -> ok. @@ -798,7 +808,7 @@ print_ext_types(#cl_state{output = Output, end. do_print_ext_types(Output, [{M,F,A}|T], Before) -> - io:format(Output, "~s~p:~p/~p\n", [Before,M,F,A]), + io:format(Output, "~s~tp:~tp/~p\n", [Before,M,F,A]), do_print_ext_types(Output, T, Before); do_print_ext_types(_, [], _) -> ok. @@ -817,10 +827,10 @@ print_warnings(#cl_state{output = Output, formatted -> [dialyzer:format_warning(W, FOpt) || W <- PrWarnings]; raw -> - [io_lib:format("~p. \n", + [io_lib:format("~tp. \n", [W]) || W <- set_warning_id(PrWarnings)] end, - io:format(Output, "\n~s", [S]) + io:format(Output, "\n~ts", [S]) end. -spec process_warnings([raw_warning()]) -> [raw_warning()]. diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index 934351aeeb..80c10183cf 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% -module(dialyzer_cl_parse). @@ -44,11 +37,12 @@ start() -> init(), Args = init:get_plain_arguments(), try - cl(Args) + Ret = cl(Args), + Ret catch throw:{dialyzer_cl_parse_error, Msg} -> {error, Msg}; _:R -> - Msg = io_lib:format("~p\n~p\n", [R, erlang:get_stacktrace()]), + Msg = io_lib:format("~tp\n~tp\n", [R, erlang:get_stacktrace()]), {error, lists:flatten(Msg)} end. @@ -88,7 +82,7 @@ cl(["--get_warnings"|T]) -> cl(["-D"|_]) -> cl_error("No defines specified after -D"); cl(["-D"++Define|T]) -> - Def = re:split(Define, "=", [{return, list}]), + Def = re:split(Define, "=", [{return, list}, unicode]), append_defines(Def), cl(T); cl(["-h"|_]) -> @@ -510,7 +504,7 @@ warning_options_msg() -> -Wno_match Suppress warnings for patterns that are unused or cannot match. -Wno_opaque - Suppress warnings for violations of opaqueness of data types. + Suppress warnings for violations of opacity of data types. -Wno_fail_call Suppress warnings for failing calls. -Wno_contracts diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 03cd9671af..5587cf2bdf 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_codeserver.erl @@ -29,18 +22,25 @@ -module(dialyzer_codeserver). -export([delete/1, - finalize_contracts/3, + store_temp_contracts/4, + give_away/2, + finalize_contracts/1, finalize_exported_types/2, - finalize_records/2, + finalize_records/1, get_contracts/1, get_callbacks/1, get_exported_types/1, + extract_exported_types/1, get_exports/1, - get_records/1, + get_records_table/1, + extract_records/1, get_next_core_label/1, - get_temp_contracts/1, + get_temp_contracts/2, + all_temp_modules/1, + store_contracts/4, get_temp_exported_types/1, - get_temp_records/1, + get_temp_records_table/1, + lookup_temp_mod_records/2, insert/3, insert_exports/2, insert_temp_exported_types/2, @@ -48,30 +48,29 @@ is_exported/2, lookup_mod_code/2, lookup_mfa_code/2, + lookup_mfa_var_label/2, lookup_mod_records/2, lookup_mod_contracts/2, lookup_mfa_contract/2, lookup_meta_info/2, new/0, set_next_core_label/2, - set_temp_records/2, store_temp_records/3, - store_temp_contracts/4]). + translate_fake_file/3]). --export_type([codeserver/0, fun_meta_info/0]). +-export_type([codeserver/0, fun_meta_info/0, contracts/0]). -include("dialyzer.hrl"). %%-------------------------------------------------------------------- -type dict_ets() :: ets:tid(). +-type map_ets() :: ets:tid(). -type set_ets() :: ets:tid(). -type types() :: erl_types:type_table(). --type mod_records() :: dict:dict(module(), types()). --type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()). --type mod_contracts() :: dict:dict(module(), contracts()). +-type contracts() :: #{mfa() => dialyzer_contracts:file_contract()}. %% A property-list of data compiled from -compile and -dialyzer attributes. -type meta_info() :: [{{'nowarn_function' | dial_warn_tag()}, @@ -81,16 +80,16 @@ -record(codeserver, {next_core_label = 0 :: label(), code :: dict_ets(), - exported_types :: set_ets() | 'undefined', % set(mfa()) - records :: dict_ets() | 'undefined', - contracts :: dict_ets() | 'undefined', - callbacks :: dict_ets() | 'undefined', + exported_types :: 'clean' | set_ets(), % set(mfa()) + records :: 'clean' | map_ets(), + contracts :: map_ets(), + callbacks :: map_ets(), fun_meta_info :: dict_ets(), % {mfa(), meta_info()} exports :: 'clean' | set_ets(), % set(mfa()) temp_exported_types :: 'clean' | set_ets(), % set(mfa()) - temp_records :: 'clean' | dict_ets(), - temp_contracts :: 'clean' | dict_ets(), - temp_callbacks :: 'clean' | dict_ets() + temp_records :: 'clean' | map_ets(), + temp_contracts :: 'clean' | map_ets(), + temp_callbacks :: 'clean' | map_ets() }). -opaque codeserver() :: #codeserver{}. @@ -104,13 +103,10 @@ ets_dict_find(Key, Table) -> _:_ -> error end. -ets_dict_store(Key, Element, Table) -> +ets_map_store(Key, Element, Table) -> true = ets:insert(Table, {Key, Element}), Table. -ets_dict_store_dict(Dict, Table) -> - true = ets:insert(Table, dict:to_list(Dict)). - ets_dict_to_dict(Table) -> Fold = fun({Key,Value}, Dict) -> dict:store(Key, Value, Dict) end, ets:foldl(Fold, dict:new(), Table). @@ -128,9 +124,6 @@ ets_set_to_set(Table) -> Fold = fun({E}, Set) -> sets:add_element(E, Set) end, ets:foldl(Fold, sets:new(), Table). -ets_read_concurrent_table(Name) -> - ets:new(Name, [{read_concurrency, true}]). - %%-------------------------------------------------------------------- -spec new() -> codeserver(). @@ -138,6 +131,13 @@ ets_read_concurrent_table(Name) -> new() -> CodeOptions = [compressed, public, {read_concurrency, true}], Code = ets:new(dialyzer_codeserver_code, CodeOptions), + ReadOptions = [compressed, {read_concurrency, true}], + [Contracts, Callbacks, Records, ExportedTypes] = + [ets:new(Name, ReadOptions) || + Name <- [dialyzer_codeserver_contracts, + dialyzer_codeserver_callbacks, + dialyzer_codeserver_records, + dialyzer_codeserver_exported_types]], TempOptions = [public, {write_concurrency, true}], [Exports, FunMetaInfo, TempExportedTypes, TempRecords, TempContracts, TempCallbacks] = @@ -150,6 +150,10 @@ new() -> #codeserver{code = Code, exports = Exports, fun_meta_info = FunMetaInfo, + exported_types = ExportedTypes, + records = Records, + contracts = Contracts, + callbacks = Callbacks, temp_exported_types = TempExportedTypes, temp_records = TempRecords, temp_contracts = TempContracts, @@ -157,11 +161,8 @@ new() -> -spec delete(codeserver()) -> 'ok'. -delete(#codeserver{code = Code, exported_types = ExportedTypes, - records = Records, contracts = Contracts, - callbacks = Callbacks}) -> - lists:foreach(fun ets:delete/1, - [Code, ExportedTypes, Records, Contracts, Callbacks]). +delete(CServer) -> + lists:foreach(fun(Table) -> true = ets:delete(Table) end, tables(CServer)). -spec insert(atom(), cerl:c_module(), codeserver()) -> codeserver(). @@ -170,13 +171,15 @@ insert(Mod, ModCode, CS) -> Exports = cerl:module_exports(ModCode), Attrs = cerl:module_attrs(ModCode), Defs = cerl:module_defs(ModCode), + {Files, SmallDefs} = compress_file_anno(Defs), As = cerl:get_ann(ModCode), Funs = [{{Mod, cerl:fname_id(Var), cerl:fname_arity(Var)}, - Val} || Val = {Var, _Fun} <- Defs], - Keys = [Key || {Key, _Value} <- Funs], + Val, {Var, cerl_trees:get_label(Fun)}} || Val = {Var, Fun} <- SmallDefs], + Keys = [Key || {Key, _Value, _Label} <- Funs], ModEntry = {Mod, {Name, Exports, Attrs, Keys, As}}, - true = ets:insert(CS#codeserver.code, [ModEntry|Funs]), + ModFileEntry = {{mod, Mod}, Files}, + true = ets:insert(CS#codeserver.code, [ModEntry, ModFileEntry|Funs]), CS. -spec get_temp_exported_types(codeserver()) -> sets:set(mfa()). @@ -213,6 +216,11 @@ is_exported(MFA, #codeserver{exports = Exports}) -> get_exported_types(#codeserver{exported_types = ExpTypes}) -> ets_set_to_set(ExpTypes). +-spec extract_exported_types(codeserver()) -> {codeserver(), set_ets()}. + +extract_exported_types(#codeserver{exported_types = ExpTypes} = CS) -> + {CS#codeserver{exported_types = 'clean'}, ExpTypes}. + -spec get_exports(codeserver()) -> sets:set(mfa()). get_exports(#codeserver{exports = Exports}) -> @@ -220,12 +228,12 @@ get_exports(#codeserver{exports = Exports}) -> -spec finalize_exported_types(sets:set(mfa()), codeserver()) -> codeserver(). -finalize_exported_types(Set, CS) -> - ExportedTypes = ets_read_concurrent_table(dialyzer_codeserver_exported_types), +finalize_exported_types(Set, + #codeserver{exported_types = ExportedTypes, + temp_exported_types = TempETypes} = CS) -> true = ets_set_insert_set(Set, ExportedTypes), - TempExpTypes = CS#codeserver.temp_exported_types, - true = ets:delete(TempExpTypes), - CS#codeserver{exported_types = ExportedTypes, temp_exported_types = clean}. + true = ets:delete(TempETypes), + CS#codeserver{temp_exported_types = clean}. -spec lookup_mod_code(atom(), codeserver()) -> cerl:c_module(). @@ -237,6 +245,11 @@ lookup_mod_code(Mod, CS) when is_atom(Mod) -> lookup_mfa_code({_M, _F, _A} = MFA, CS) -> table__lookup(CS#codeserver.code, MFA). +-spec lookup_mfa_var_label(mfa(), codeserver()) -> {cerl:c_var(), label()}. + +lookup_mfa_var_label({_M, _F, _A} = MFA, CS) -> + ets:lookup_element(CS#codeserver.code, MFA, 3). + -spec get_next_core_label(codeserver()) -> label(). get_next_core_label(#codeserver{next_core_label = NCL}) -> @@ -251,53 +264,78 @@ set_next_core_label(NCL, CS) -> lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) -> case ets_dict_find(Mod, RecDict) of - error -> dict:new(); - {ok, Dict} -> Dict + error -> maps:new(); + {ok, Map} -> Map end. --spec get_records(codeserver()) -> mod_records(). +-spec get_records_table(codeserver()) -> map_ets(). + +get_records_table(#codeserver{records = RecDict}) -> + RecDict. + +-spec extract_records(codeserver()) -> {codeserver(), map_ets()}. -get_records(#codeserver{records = RecDict}) -> - ets_dict_to_dict(RecDict). +extract_records(#codeserver{records = RecDict} = CS) -> + {CS#codeserver{records = clean}, RecDict}. -spec store_temp_records(module(), types(), codeserver()) -> codeserver(). -store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS) +store_temp_records(Mod, Map, #codeserver{temp_records = TempRecDict} = CS) when is_atom(Mod) -> - case dict:size(Dict) =:= 0 of + case maps:size(Map) =:= 0 of true -> CS; - false -> CS#codeserver{temp_records = ets_dict_store(Mod, Dict, TempRecDict)} + false -> CS#codeserver{temp_records = ets_map_store(Mod, Map, TempRecDict)} end. --spec get_temp_records(codeserver()) -> mod_records(). +-spec get_temp_records_table(codeserver()) -> map_ets(). -get_temp_records(#codeserver{temp_records = TempRecDict}) -> - ets_dict_to_dict(TempRecDict). +get_temp_records_table(#codeserver{temp_records = TempRecDict}) -> + TempRecDict. --spec set_temp_records(mod_records(), codeserver()) -> codeserver(). +-spec lookup_temp_mod_records(module(), codeserver()) -> types(). -set_temp_records(Dict, CS) -> - true = ets:delete(CS#codeserver.temp_records), - TempRecords = ets:new(dialyzer_codeserver_temp_records,[]), - true = ets_dict_store_dict(Dict, TempRecords), - CS#codeserver{temp_records = TempRecords}. - --spec finalize_records(mod_records(), codeserver()) -> codeserver(). +lookup_temp_mod_records(Mod, #codeserver{temp_records = TempRecDict}) -> + case ets_dict_find(Mod, TempRecDict) of + error -> maps:new(); + {ok, Map} -> Map + end. -finalize_records(Dict, CS) -> - true = ets:delete(CS#codeserver.temp_records), - Records = ets_read_concurrent_table(dialyzer_codeserver_records), - true = ets_dict_store_dict(Dict, Records), - CS#codeserver{records = Records, temp_records = clean}. +-spec finalize_records(codeserver()) -> codeserver(). + +finalize_records(#codeserver{temp_records = TmpRecords, + records = Records} = CS) -> + %% The annotations of the abstract code are reset as they are no + %% longer needed, which makes the ETS table compression better. + A0 = erl_anno:new(0), + AFun = fun(_) -> A0 end, + FFun = fun({F, Abs, Type}) -> + NewAbs = erl_parse:map_anno(AFun, Abs), + {F, NewAbs, Type} + end, + ArFun = fun({Arity, Fields}) -> {Arity, lists:map(FFun, Fields)} end, + List = dialyzer_utils:ets_tab2list(TmpRecords), + true = ets:delete(TmpRecords), + Fun = fun({Mod, Map}) -> + MFun = + fun({record, _}, {FileLine, ArityFields}) -> + {FileLine, lists:map(ArFun, ArityFields)}; + (_, {{M, FileLine, Abs, Args}, Type}) -> + {{M, FileLine, erl_parse:map_anno(AFun, Abs), Args}, Type} + end, + {Mod, maps:map(MFun, Map)} + end, + NewList = lists:map(Fun, List), + true = ets:insert(Records, NewList), + CS#codeserver{temp_records = clean}. -spec lookup_mod_contracts(atom(), codeserver()) -> contracts(). lookup_mod_contracts(Mod, #codeserver{contracts = ContDict}) when is_atom(Mod) -> case ets_dict_find(Mod, ContDict) of - error -> dict:new(); + error -> maps:new(); {ok, Keys} -> - dict:from_list([get_file_contract(Key, ContDict)|| Key <- Keys]) + maps:from_list([get_file_contract(Key, ContDict)|| Key <- Keys]) end. get_file_contract(Key, ContDict) -> @@ -317,10 +355,13 @@ lookup_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) -> {ok, PropList} -> PropList end. --spec get_contracts(codeserver()) -> mod_contracts(). +-spec get_contracts(codeserver()) -> + dict:dict(mfa(), dialyzer_contracts:file_contract()). get_contracts(#codeserver{contracts = ContDict}) -> - ets_dict_to_dict(ContDict). + dict:filter(fun({_M, _F, _A}, _) -> true; + (_, _) -> false + end, ets_dict_to_dict(ContDict)). -spec get_callbacks(codeserver()) -> list(). @@ -330,48 +371,79 @@ get_callbacks(#codeserver{callbacks = CallbDict}) -> -spec store_temp_contracts(module(), contracts(), contracts(), codeserver()) -> codeserver(). -store_temp_contracts(Mod, SpecDict, CallbackDict, +store_temp_contracts(Mod, SpecMap, CallbackMap, #codeserver{temp_contracts = Cn, temp_callbacks = Cb} = CS) when is_atom(Mod) -> - CS1 = - case dict:size(SpecDict) =:= 0 of - true -> CS; - false -> - CS#codeserver{temp_contracts = ets_dict_store(Mod, SpecDict, Cn)} - end, - case dict:size(CallbackDict) =:= 0 of - true -> CS1; - false -> - CS1#codeserver{temp_callbacks = ets_dict_store(Mod, CallbackDict, Cb)} - end. - --spec get_temp_contracts(codeserver()) -> {mod_contracts(), mod_contracts()}. + %% Make sure Mod is stored even if there are no callbacks or + %% contracts. + CS1 = CS#codeserver{temp_contracts = ets_map_store(Mod, SpecMap, Cn)}, + CS1#codeserver{temp_callbacks = ets_map_store(Mod, CallbackMap, Cb)}. -get_temp_contracts(#codeserver{temp_contracts = TempContDict, - temp_callbacks = TempCallDict}) -> - {ets_dict_to_dict(TempContDict), ets_dict_to_dict(TempCallDict)}. +-spec all_temp_modules(codeserver()) -> [module()]. --spec finalize_contracts(mod_contracts(), mod_contracts(), codeserver()) -> - codeserver(). +all_temp_modules(#codeserver{temp_contracts = TempContTable}) -> + ets:select(TempContTable, [{{'$1', '$2'}, [], ['$1']}]). -finalize_contracts(SpecDict, CallbackDict, CS) -> - Contracts = ets_read_concurrent_table(dialyzer_codeserver_contracts), - Callbacks = ets_read_concurrent_table(dialyzer_codeserver_callbacks), - Contracts = dict:fold(fun decompose_spec_dict/3, Contracts, SpecDict), - Callbacks = dict:fold(fun decompose_cb_dict/3, Callbacks, CallbackDict), - CS#codeserver{contracts = Contracts, callbacks = Callbacks, - temp_contracts = clean, temp_callbacks = clean}. +-spec store_contracts(module(), contracts(), contracts(), codeserver()) -> + codeserver(). -decompose_spec_dict(Mod, Dict, Table) -> - Keys = dict:fetch_keys(Dict), - true = ets:insert(Table, dict:to_list(Dict)), - true = ets:insert(Table, {Mod, Keys}), - Table. +store_contracts(Mod, SpecMap, CallbackMap, CS) -> + #codeserver{contracts = SpecDict, callbacks = CallbackDict} = CS, + Keys = maps:keys(SpecMap), + true = ets:insert(SpecDict, maps:to_list(SpecMap)), + true = ets:insert(SpecDict, {Mod, Keys}), + true = ets:insert(CallbackDict, maps:to_list(CallbackMap)), + CS. -decompose_cb_dict(_Mod, Dict, Table) -> - true = ets:insert(Table, dict:to_list(Dict)), - Table. +-spec get_temp_contracts(module(), codeserver()) -> + {contracts(), contracts()}. + +get_temp_contracts(Mod, #codeserver{temp_contracts = TempContDict, + temp_callbacks = TempCallDict}) -> + [{Mod, Contracts}] = ets:lookup(TempContDict, Mod), + true = ets:delete(TempContDict, Mod), + [{Mod, Callbacks}] = ets:lookup(TempCallDict, Mod), + true = ets:delete(TempCallDict, Mod), + {Contracts, Callbacks}. + +-spec give_away(codeserver(), pid()) -> 'ok'. + +give_away(CServer, Pid) -> + lists:foreach(fun(Table) -> true = ets:give_away(Table, Pid, any) + end, tables(CServer)). + +tables(#codeserver{code = Code, + fun_meta_info = FunMetaInfo, + exports = Exports, + temp_exported_types = TempExpTypes, + temp_records = TempRecords, + temp_contracts = TempContracts, + temp_callbacks = TempCallbacks, + exported_types = ExportedTypes, + records = Records, + contracts = Contracts, + callbacks = Callbacks}) -> + [Table || Table <- [Code, FunMetaInfo, Exports, TempExpTypes, + TempRecords, TempContracts, TempCallbacks, + ExportedTypes, Records, Contracts, Callbacks], + Table =/= clean]. + +-spec finalize_contracts(codeserver()) -> codeserver(). + +finalize_contracts(#codeserver{temp_contracts = TempContDict, + temp_callbacks = TempCallDict} = CS) -> + true = ets:delete(TempContDict), + true = ets:delete(TempCallDict), + CS#codeserver{temp_contracts = clean, temp_callbacks = clean}. + +-spec translate_fake_file(codeserver(), module(), file:filename()) -> + file:filename(). + +translate_fake_file(#codeserver{code = Code}, Module, FakeFile) -> + Files = ets:lookup_element(Code, {mod, Module}, 2), + {FakeFile, File} = lists:keyfind(FakeFile, 1, Files), + File. table__lookup(TablePid, M) when is_atom(M) -> {Name, Exports, Attrs, Keys, As} = ets:lookup_element(TablePid, M, 2), @@ -379,3 +451,25 @@ table__lookup(TablePid, M) when is_atom(M) -> cerl:ann_c_module(As, Name, Exports, Attrs, Defs); table__lookup(TablePid, MFA) -> ets:lookup_element(TablePid, MFA, 2). + +compress_file_anno(Term) -> + {Files, SmallTerm} = compress_file_anno(Term, []), + {[{FakeFile, File} || {File, {file, FakeFile}} <- Files], SmallTerm}. + +compress_file_anno({file, F}, Fs) when is_list(F) -> + case lists:keyfind(F, 1, Fs) of + false -> + I = integer_to_list(length(Fs)), + FileI = {file, I}, + NFs = [{F, FileI}|Fs], + {NFs, FileI}; + {F, FileI} -> {Fs, FileI} + end; +compress_file_anno(T, Fs) when is_tuple(T) -> + {NFs, NL} = compress_file_anno(tuple_to_list(T), Fs), + {NFs, list_to_tuple(NL)}; +compress_file_anno([E|L], Fs) -> + {Fs1, NE} = compress_file_anno(E, Fs), + {NFs, NL} = compress_file_anno(L, Fs1), + {NFs, [NE|NL]}; +compress_file_anno(T, Fs) -> {Fs, T}. diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 976a2b8955..e72c1aecfc 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% -module(dialyzer_contracts). @@ -25,7 +18,7 @@ check_contracts/4, contracts_without_fun/3, contract_to_string/1, - get_invalid_contract_warnings/4, + get_invalid_contract_warnings/3, get_contract_args/1, get_contract_return/1, get_contract_return/2, @@ -147,55 +140,53 @@ sequence([H], _Delimiter) -> H; sequence([H|T], Delimiter) -> H ++ Delimiter ++ sequence(T, Delimiter). -spec process_contract_remote_types(dialyzer_codeserver:codeserver()) -> - dialyzer_codeserver:codeserver(). + dialyzer_codeserver:codeserver(). process_contract_remote_types(CodeServer) -> - {TmpContractDict, TmpCallbackDict} = - dialyzer_codeserver:get_temp_contracts(CodeServer), + Mods = dialyzer_codeserver:all_temp_modules(CodeServer), + RecordTable = dialyzer_codeserver:get_records_table(CodeServer), ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer), - RecordDict = dialyzer_codeserver:get_records(CodeServer), ContractFun = fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) -> #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract, {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) -> - CFun(ExpTypes, RecordDict, C1) + CFun(ExpTypes, RecordTable, C1) end, C0, CFuns), Args = general_domain(NewCs), Contract = #contract{contracts = NewCs, args = Args, forms = Forms}, {{MFA, {File, Contract, Xtra}}, C2} end, ModuleFun = - fun({ModuleName, ContractDict}, C3) -> - {NewContractList, C4} = - lists:mapfoldl(ContractFun, C3, dict:to_list(ContractDict)), - {{ModuleName, dict:from_list(NewContractList)}, C4} + fun(ModuleName) -> + Cache = erl_types:cache__new(), + {ContractMap, CallbackMap} = + dialyzer_codeserver:get_temp_contracts(ModuleName, CodeServer), + {NewContractList, Cache1} = + lists:mapfoldl(ContractFun, Cache, maps:to_list(ContractMap)), + {NewCallbackList, _NewCache} = + lists:mapfoldl(ContractFun, Cache1, maps:to_list(CallbackMap)), + dialyzer_codeserver:store_contracts(ModuleName, + maps:from_list(NewContractList), + maps:from_list(NewCallbackList), + CodeServer) end, - Cache = erl_types:cache__new(), - {NewContractList, C5} = - lists:mapfoldl(ModuleFun, Cache, dict:to_list(TmpContractDict)), - {NewCallbackList, _C6} = - lists:mapfoldl(ModuleFun, C5, dict:to_list(TmpCallbackDict)), - NewContractDict = dict:from_list(NewContractList), - NewCallbackDict = dict:from_list(NewCallbackList), - dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict, - CodeServer). - --type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]). + lists:foreach(ModuleFun, Mods), + dialyzer_codeserver:finalize_contracts(CodeServer). --type fun_types() :: dict:dict(label(), erl_types:type_table()). +-type fun_types() :: orddict:orddict(label(), erl_types:type_table()). --spec check_contracts([{mfa(), file_contract()}], +-spec check_contracts(orddict:orddict(mfa(), #contract{}), dialyzer_callgraph:callgraph(), fun_types(), - opaques_fun()) -> plt_contracts(). + erl_types:opaques()) -> plt_contracts(). -check_contracts(Contracts, Callgraph, FunTypes, FindOpaques) -> +check_contracts(Contracts, Callgraph, FunTypes, ModOpaques) -> FoldFun = - fun(Label, Type, NewContracts) -> + fun({Label, Type}, NewContracts) -> case dialyzer_callgraph:lookup_name(Label, Callgraph) of {ok, {M,F,A} = MFA} -> case orddict:find(MFA, Contracts) of - {ok, {_FileLine, Contract, _Xtra}} -> - Opaques = FindOpaques(M), + {ok, Contract} -> + {M, Opaques} = lists:keyfind(M, 1, ModOpaques), case check_contract(Contract, Type, Opaques) of ok -> case erl_bif_types:is_known(M, F, A) of @@ -213,7 +204,7 @@ check_contracts(Contracts, Callgraph, FunTypes, FindOpaques) -> error -> NewContracts end end, - dict:fold(FoldFun, [], FunTypes). + orddict:from_list(lists:foldl(FoldFun, [], orddict:to_list(FunTypes))). %% Checks all components of a contract -spec check_contract(#contract{}, erl_types:erl_type()) -> 'ok' | {'error', term()}. @@ -221,6 +212,9 @@ check_contracts(Contracts, Callgraph, FunTypes, FindOpaques) -> check_contract(Contract, SuccType) -> check_contract(Contract, SuccType, 'universe'). +-spec check_contract(#contract{}, erl_types:erl_type(), erl_types:opaques()) -> + 'ok' | {'error', term()}. + check_contract(#contract{contracts = Contracts}, SuccType, Opaques) -> try Contracts1 = [{Contract, insert_constraints(Constraints)} @@ -232,7 +226,7 @@ check_contract(#contract{contracts = Contracts}, SuccType, Opaques) -> error -> {error, {overlapping_contract, []}}; ok -> - InfList = [erl_types:t_inf(Contract, SuccType, Opaques) + InfList = [{Contract, erl_types:t_inf(Contract, SuccType, Opaques)} || Contract <- Contracts2], case check_contract_inf_list(InfList, SuccType, Opaques) of {error, _} = Invalid -> Invalid; @@ -256,10 +250,21 @@ check_domains([Dom|Doms]) -> %% Allow a contract if one of the overloaded contracts is possible. %% We used to be more strict, e.g., all overloaded contracts had to be %% possible. -check_contract_inf_list([FunType|Left], SuccType, Opaques) -> +check_contract_inf_list(List, SuccType, Opaques) -> + case check_contract_inf_list(List, SuccType, Opaques, []) of + ok -> ok; + {error, []} -> {error, invalid_contract}; + {error, [{SigRange, ContrRange}|_]} -> + case erl_types:t_find_opaque_mismatch(SigRange, ContrRange, Opaques) of + error -> {error, invalid_contract}; + {ok, _T1, T2} -> {error, {opaque_mismatch, T2}} + end + end. + +check_contract_inf_list([{Contract, FunType}|Left], SuccType, Opaques, OM) -> FunArgs = erl_types:t_fun_args(FunType), case lists:any(fun erl_types:t_is_none_or_unit/1, FunArgs) of - true -> check_contract_inf_list(Left, SuccType, Opaques); + true -> check_contract_inf_list(Left, SuccType, Opaques, OM); false -> STRange = erl_types:t_fun_range(SuccType), case erl_types:t_is_none_or_unit(STRange) of @@ -267,13 +272,16 @@ check_contract_inf_list([FunType|Left], SuccType, Opaques) -> false -> Range = erl_types:t_fun_range(FunType), case erl_types:t_is_none(erl_types:t_inf(STRange, Range)) of - true -> check_contract_inf_list(Left, SuccType, Opaques); + true -> + CR = erl_types:t_fun_range(Contract), + NewOM = [{STRange, CR}|OM], + check_contract_inf_list(Left, SuccType, Opaques, NewOM); false -> ok end end end; -check_contract_inf_list([], _SuccType, _Opaques) -> - {error, invalid_contract}. +check_contract_inf_list([], _SuccType, _Opaques, OM) -> + {error, OM}. check_extraneous([], _SuccType) -> ok; check_extraneous([C|Cs], SuccType) -> @@ -286,7 +294,7 @@ check_extraneous_1(Contract, SuccType) -> CRng = erl_types:t_fun_range(Contract), CRngs = erl_types:t_elements(CRng), STRng = erl_types:t_fun_range(SuccType), - ?debug("CR = ~p\nSR = ~p\n", [CRngs, STRng]), + ?debug("CR = ~tp\nSR = ~tp\n", [CRngs, STRng]), case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng))] of [] -> @@ -353,7 +361,7 @@ process_contract({Contract, Constraints}, CallTypes0) -> CallTypesFun = erl_types:t_fun(CallTypes0, erl_types:t_any()), ContArgsFun = erl_types:t_fun(erl_types:t_fun_args(Contract), erl_types:t_any()), - ?debug("Instance: Contract: ~s\n Arguments: ~s\n", + ?debug("Instance: Contract: ~ts\n Arguments: ~ts\n", [erl_types:t_to_string(ContArgsFun), erl_types:t_to_string(CallTypesFun)]), case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of @@ -383,7 +391,7 @@ solve_constraints(Contract, Call, Constraints) -> %% ?debug("Inf: ~s\n", [erl_types:t_to_string(Inf)]), %% erl_types:t_assign_variables_to_subtype(Contract, Inf). --type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()). +-type contracts() :: dialyzer_codeserver:contracts(). %% Checks the contracts for functions that are not implemented -spec contracts_without_fun(contracts(), [_], dialyzer_callgraph:callgraph()) -> @@ -393,12 +401,12 @@ contracts_without_fun(Contracts, AllFuns0, Callgraph) -> AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity} || {Label, Arity} <- AllFuns0], AllFuns2 = [{M, F, A} || {{ok, {M, F, _}}, A} <- AllFuns1], - AllContractMFAs = dict:fetch_keys(Contracts), + AllContractMFAs = maps:keys(Contracts), ErrorContractMFAs = AllContractMFAs -- AllFuns2, [warn_spec_missing_fun(MFA, Contracts) || MFA <- ErrorContractMFAs]. warn_spec_missing_fun({M, F, A} = MFA, Contracts) -> - {{File, Line}, _Contract, _Xtra} = dict:fetch(MFA, Contracts), + {{File, Line}, _Contract, _Xtra} = maps:get(MFA, Contracts), WarningInfo = {File, Line, MFA}, {?WARN_CONTRACT_SYNTAX, WarningInfo, {spec_missing_fun, [M, F, A]}}. @@ -420,7 +428,7 @@ insert_constraints([{subtype, Type1, Type2}|Left], Map) -> false -> %% A lot of things should change to add supertypes throw({error, io_lib:format("First argument of is_subtype constraint " - "must be a type variable: ~p\n", [Type1])}) + "must be a type variable: ~tp\n", [Type1])}) end; insert_constraints([], Map) -> Map. @@ -431,11 +439,11 @@ insert_constraints([], Map) -> Map. -spec store_tmp_contract(mfa(), file_line(), spec_data(), contracts(), types()) -> contracts(). -store_tmp_contract(MFA, FileLine, {TypeSpec, Xtra}, SpecDict, RecordsDict) -> - %% io:format("contract from form: ~p\n", [TypeSpec]), +store_tmp_contract(MFA, FileLine, {TypeSpec, Xtra}, SpecMap, RecordsDict) -> + %% io:format("contract from form: ~tp\n", [TypeSpec]), TmpContract = contract_from_form(TypeSpec, MFA, RecordsDict, FileLine), - %% io:format("contract: ~p\n", [TmpContract]), - dict:store(MFA, {FileLine, TmpContract, Xtra}, SpecDict). + %% io:format("contract: ~tp\n", [TmpContract]), + maps:put(MFA, {FileLine, TmpContract, Xtra}, SpecMap). contract_from_form(Forms, MFA, RecDict, FileLine) -> {CFuns, Forms1} = contract_from_form(Forms, MFA, RecDict, FileLine, [], []), @@ -444,15 +452,15 @@ contract_from_form(Forms, MFA, RecDict, FileLine) -> contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = - fun(ExpTypes, AllRecords, Cache) -> + fun(ExpTypes, RecordTable, Cache) -> {NewType, NewCache} = try - from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) + from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache) catch throw:{error, Msg} -> {File, Line} = FileLine, - NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File), - Line, Msg]), + NewMsg = io_lib:format("~ts:~p: ~ts", [filename:basename(File), + Line, Msg]), throw({error, NewMsg}) end, NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), @@ -465,12 +473,12 @@ contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], MFA, RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = - fun(ExpTypes, AllRecords, Cache) -> + fun(ExpTypes, RecordTable, Cache) -> {Constr1, VarTable, Cache1} = - process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords, + process_constraints(Constr, MFA, RecDict, ExpTypes, RecordTable, Cache), {NewType, NewCache} = - from_form_with_check(Form, ExpTypes, MFA, AllRecords, + from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache1), NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), {{NewTypeNoVars, Constr1}, NewCache} @@ -481,47 +489,47 @@ contract_from_form([{type, _L1, bounded_fun, contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. -process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> +process_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, - AllRecords, Cache), + RecordTable, Cache), Init = remove_cycles(Init0), - constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords, NewCache). + constraints_fixpoint(Init, MFA, RecDict, ExpTypes, RecordTable, NewCache). -initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> - initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, +initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> + initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache, []). -initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, +initialize_constraints([], _MFA, _RecDict, _ExpTypes, _RecordTable, Cache, Acc) -> {Acc, Cache}; -initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, +initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, RecordTable, Cache, Acc) -> case Constr of {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} -> VarTable = erl_types:var_table__new(), {T1, NewCache} = - final_form(Type1, ExpTypes, MFA, AllRecords, VarTable, Cache), + final_form(Type1, ExpTypes, MFA, RecordTable, VarTable, Cache), Entry = {T1, Type2}, - initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, + initialize_constraints(Rest, MFA, RecDict, ExpTypes, RecordTable, NewCache, [Entry|Acc]); {type, _, constraint, [{atom,_,Name}, List]} -> N = length(List), throw({error, - io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])}) + io_lib:format("Unsupported type guard ~tw/~w\n", [Name, N])}) end. -constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> +constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) -> VarTable = erl_types:var_table__new(), {VarTab, NewCache} = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, + constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, VarTable, Cache), constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes, - AllRecords, NewCache). + RecordTable, NewCache). constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes, - AllRecords, Cache) -> + RecordTable, Cache) -> {NewVarTab, NewCache} = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, + constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, OldVarTab, Cache), case NewVarTab of OldVarTab -> @@ -533,38 +541,41 @@ constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes, {FinalConstrs, NewVarTab, NewCache}; _Other -> constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes, - AllRecords, NewCache) + RecordTable, NewCache) end. -final_form(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) -> - from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache). +final_form(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) -> + from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache). -from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) -> +from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache) -> VarTable = erl_types:var_table__new(), - from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache). + from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache). -from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) -> +from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) -> Site = {spec, MFA}, - C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, + C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, RecordTable, VarTable, Cache), - erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarTable, C1). + %% The check costs some time, and with the assumption that contracts + %% are not very deep, it does not add anything. + %% erl_types:t_from_form_check_remote(Form, ExpTypes, MFA, RecordTable), + erl_types:t_from_form(Form, ExpTypes, Site, RecordTable, VarTable, C1). -constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, +constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable, VarTab, Cache) -> {Subtypes, NewCache} = - constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, + constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, RecordTable, VarTab, Cache, []), {insert_constraints(Subtypes), NewCache}. -constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, +constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _RecordTable, _VarTab, Cache, Acc) -> {Acc, Cache}; -constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, AllRecords, +constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, RecordTable, VarTab, Cache, Acc) -> {T2, NewCache} = - final_form(Form2, ExpTypes, MFA, AllRecords, VarTab, Cache), + final_form(Form2, ExpTypes, MFA, RecordTable, VarTab, Cache), NewAcc = [{subtype, T1, T2}|Acc], - constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords, + constraints_to_subs(Rest, MFA, RecDict, ExpTypes, RecordTable, VarTab, NewCache, NewAcc). %% Replaces variables with '_' when necessary to break up cycles among @@ -655,38 +666,46 @@ general_domain([], AccSig) -> -spec get_invalid_contract_warnings([module()], dialyzer_codeserver:codeserver(), - dialyzer_plt:plt(), - opaques_fun()) -> [raw_warning()]. + dialyzer_plt:plt()) -> [raw_warning()]. -get_invalid_contract_warnings(Modules, CodeServer, Plt, FindOpaques) -> - get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, FindOpaques, []). +get_invalid_contract_warnings(Modules, CodeServer, Plt) -> + get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, []). -get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, FindOpaques, Acc) -> +get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, Acc) -> Contracts1 = dialyzer_codeserver:lookup_mod_contracts(Mod, CodeServer), - Contracts2 = dict:to_list(Contracts1), - Records = dialyzer_codeserver:lookup_mod_records(Mod, CodeServer), - NewAcc = get_invalid_contract_warnings_funs(Contracts2, Plt, Records, FindOpaques, Acc), - get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, FindOpaques, NewAcc); -get_invalid_contract_warnings_modules([], _CodeServer, _Plt, _FindOpaques, Acc) -> + NewAcc = + case maps:size(Contracts1) =:= 0 of + true -> Acc; + false -> + Contracts2 = maps:to_list(Contracts1), + Records = dialyzer_codeserver:lookup_mod_records(Mod, CodeServer), + Opaques = erl_types:t_opaque_from_records(Records), + get_invalid_contract_warnings_funs(Contracts2, Plt, Records, + Opaques, Acc) + end, + get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, NewAcc); +get_invalid_contract_warnings_modules([], _CodeServer, _Plt, Acc) -> Acc. get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left], - Plt, RecDict, FindOpaques, Acc) -> + Plt, RecDict, Opaques, Acc) -> case dialyzer_plt:lookup(Plt, MFA) of none -> %% This must be a contract for a non-available function. Just accept it. - get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, Acc); + get_invalid_contract_warnings_funs(Left, Plt, RecDict, Opaques, Acc); {value, {Ret, Args}} -> Sig = erl_types:t_fun(Args, Ret), {M, _F, _A} = MFA, - %% io:format("MFA ~p~n", [MFA]), - Opaques = FindOpaques(M), + %% io:format("MFA ~tp~n", [MFA]), {File, Line} = FileLine, WarningInfo = {File, Line, MFA}, NewAcc = case check_contract(Contract, Sig, Opaques) of {error, invalid_contract} -> [invalid_contract_warning(MFA, WarningInfo, Sig, RecDict)|Acc]; + {error, {opaque_mismatch, T2}} -> + W = contract_opaque_warning(MFA, WarningInfo, T2, Sig, RecDict), + [W|Acc]; {error, {overlapping_contract, []}} -> [overlapping_contract_warning(MFA, WarningInfo)|Acc]; {error, {extra_range, ExtraRanges, STRange}} -> @@ -731,15 +750,21 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left], RecDict, Acc) end end, - get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, NewAcc) + get_invalid_contract_warnings_funs(Left, Plt, RecDict, Opaques, NewAcc) end; -get_invalid_contract_warnings_funs([], _Plt, _RecDict, _FindOpaques, Acc) -> +get_invalid_contract_warnings_funs([], _Plt, _RecDict, _Opaques, Acc) -> Acc. invalid_contract_warning({M, F, A}, WarningInfo, SuccType, RecDict) -> SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict), {?WARN_CONTRACT_TYPES, WarningInfo, {invalid_contract, [M, F, A, SuccTypeStr]}}. +contract_opaque_warning({M, F, A}, WarningInfo, OpType, SuccType, RecDict) -> + OpaqueStr = erl_types:t_to_string(OpType), + SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict), + {?WARN_CONTRACT_TYPES, WarningInfo, + {contract_with_opaque, [M, F, A, OpaqueStr, SuccTypeStr]}}. + overlapping_contract_warning({M, F, A}, WarningInfo) -> {?WARN_CONTRACT_TYPES, WarningInfo, {overlapping_contract, [M, F, A]}}. @@ -768,19 +793,9 @@ picky_contract_check(CSig0, Sig0, MFA, WarningInfo, Contract, RecDict, Acc) -> end. extra_contract_warning(MFA, WarningInfo, Contract, CSig, Sig, RecDict) -> - %% We do not want to depend upon erl_types:t_to_string() possibly - %% hiding the contents of opaque types. - SigUnopaque = erl_types:t_unopaque(Sig), - CSigUnopaque = erl_types:t_unopaque(CSig), - SigString0 = - lists:flatten(dialyzer_utils:format_sig(SigUnopaque, RecDict)), - ContractString0 = - lists:flatten(dialyzer_utils:format_sig(CSigUnopaque, RecDict)), - %% The only difference is in record fields containing 'undefined' or not. - IsUndefRecordFieldsRelated = SigString0 =:= ContractString0, {IsRemoteTypesRelated, SubtypeRelation} = is_remote_types_related(Contract, CSig, Sig, MFA, RecDict), - case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of + case IsRemoteTypesRelated of true -> no_warning; false -> @@ -828,7 +843,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) -> t_from_forms_without_remote([{FType, []}], MFA, RecDict) -> Site = {spec, MFA}, - {Type1, _} = erl_types:t_from_form_without_remote(FType, Site, RecDict), + Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict), {ok, erl_types:subst_all_vars_to_any(Type1)}; t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) -> %% 'When' constraints diff --git a/lib/dialyzer/src/dialyzer_coordinator.erl b/lib/dialyzer/src/dialyzer_coordinator.erl index 87cbd25b30..7c1bc1de5a 100644 --- a/lib/dialyzer/src/dialyzer_coordinator.erl +++ b/lib/dialyzer/src/dialyzer_coordinator.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_coordinator.erl @@ -83,6 +76,8 @@ active = 0 :: integer(), result :: result(), next_label = 0 :: integer(), + jobs :: [job()], + job_fun :: fun(), init_data :: init_data(), regulator :: regulator(), scc_to_pid :: scc_to_pid() @@ -115,16 +110,18 @@ spawn_jobs(Mode, Jobs, InitData, Timing) -> false -> unused end, Coordinator = {Collector, Regulator, SCCtoPID}, - Fold = - fun(Job, Count) -> - Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator), - case TypesigOrDataflow of - true -> true = ets:insert(SCCtoPID, {Job, Pid}), ok; - false -> ok - end, - Count + 1 + JobFun = + fun(Job) -> + Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator), + case TypesigOrDataflow of + true -> true = ets:insert(SCCtoPID, {Job, Pid}); + false -> true + end end, - JobCount = lists:foldl(Fold, 0, Jobs), + JobCount = length(Jobs), + NumberOfInitJobs = min(JobCount, 20 * dialyzer_utils:parallelism()), + {InitJobs, RestJobs} = lists:split(NumberOfInitJobs, Jobs), + lists:foreach(JobFun, InitJobs), Unit = case Mode of 'typesig' -> "SCCs"; @@ -136,11 +133,13 @@ spawn_jobs(Mode, Jobs, InitData, Timing) -> 'compile' -> dialyzer_analysis_callgraph:compile_init_result(); _ -> [] end, - #state{mode = Mode, active = JobCount, result = InitResult, next_label = 0, - init_data = InitData, regulator = Regulator, scc_to_pid = SCCtoPID}. + #state{mode = Mode, active = JobCount, result = InitResult, + next_label = 0, job_fun = JobFun, jobs = RestJobs, + init_data = InitData, regulator = Regulator, scc_to_pid = SCCtoPID}. collect_result(#state{mode = Mode, active = Active, result = Result, next_label = NextLabel, init_data = InitData, + jobs = JobsLeft, job_fun = JobFun, regulator = Regulator, scc_to_pid = SCCtoPID} = State) -> receive {next_label_request, Estimation, Pid} -> @@ -148,20 +147,35 @@ collect_result(#state{mode = Mode, active = Active, result = Result, collect_result(State#state{next_label = NextLabel + Estimation}); {done, Job, Data} -> NewResult = update_result(Mode, InitData, Job, Data, Result), + TypesigOrDataflow = (Mode =:= 'typesig') orelse (Mode =:= 'dataflow'), case Active of 1 -> kill_regulator(Regulator), case Mode of 'compile' -> {NewResult, NextLabel}; - X when X =:= 'typesig'; X =:= 'dataflow' -> + _ when TypesigOrDataflow -> ets:delete(SCCtoPID), NewResult; 'warnings' -> NewResult end; N -> - collect_result(State#state{result = NewResult, active = N - 1}) + case TypesigOrDataflow of + true -> true = ets:delete(SCCtoPID, Job); + false -> true + end, + NewJobsLeft = + case JobsLeft of + [] -> []; + [NewJob|JobsLeft1] -> + JobFun(NewJob), + JobsLeft1 + end, + NewState = State#state{result = NewResult, + jobs = NewJobsLeft, + active = N - 1}, + collect_result(NewState) end end. @@ -177,18 +191,20 @@ update_result(Mode, InitData, Job, Data, Result) -> end. -spec sccs_to_pids([scc() | module()], coordinator()) -> - {[dialyzer_worker:worker()], [scc() | module()]}. + [dialyzer_worker:worker()]. sccs_to_pids(SCCs, {_Collector, _Regulator, SCCtoPID}) -> Fold = - fun(SCC, {Pids, Unknown}) -> - try ets:lookup_element(SCCtoPID, SCC, 2) of - Result -> {[Result|Pids], Unknown} - catch - _:_ -> {Pids, [SCC|Unknown]} - end + fun(SCC, Pids) -> + %% The SCCs that SCC depends on have always been started. + try ets:lookup_element(SCCtoPID, SCC, 2) of + Pid when is_pid(Pid) -> + [Pid|Pids] + catch + _:_ -> Pids + end end, - lists:foldl(Fold, {[], []}, SCCs). + lists:foldl(Fold, [], SCCs). -spec job_done(job(), job_result(), coordinator()) -> ok. diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 9399789464..ea3523a965 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%-------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_dataflow.erl @@ -145,7 +138,7 @@ %%-------------------------------------------------------------------- --type fun_types() :: dict:dict(label(), type()). +-type fun_types() :: orddict:orddict(label(), type()). -spec get_warnings(cerl:c_module(), dialyzer_plt:plt(), dialyzer_callgraph:callgraph(), @@ -198,19 +191,19 @@ analyze_loop(State) -> {ArgTypes, IsCalled} = state__get_args_and_status(Fun, NewState1), case not IsCalled of true -> - ?debug("Not handling (not called) ~w: ~s\n", + ?debug("Not handling (not called) ~w: ~ts\n", [NewState1#state.curr_fun, t_to_string(t_product(ArgTypes))]), analyze_loop(NewState1); false -> case state__fun_env(Fun, NewState1) of none -> - ?debug("Not handling (no env) ~w: ~s\n", + ?debug("Not handling (no env) ~w: ~ts\n", [NewState1#state.curr_fun, t_to_string(t_product(ArgTypes))]), analyze_loop(NewState1); Map -> - ?debug("Handling fun ~p: ~s\n", + ?debug("Handling fun ~p: ~ts\n", [NewState1#state.curr_fun, t_to_string(state__fun_type(Fun, NewState1))]), Vars = cerl:fun_vars(Fun), @@ -229,7 +222,7 @@ analyze_loop(State) -> end, {NewState4, _Map2, BodyType} = traverse(Body, Map1, NewState3), - ?debug("Done analyzing: ~w:~s\n", + ?debug("Done analyzing: ~w:~ts\n", [NewState1#state.curr_fun, t_to_string(t_fun(ArgTypes, BodyType))]), NewState5 = @@ -239,7 +232,7 @@ analyze_loop(State) -> end, NewState6 = state__update_fun_entry(Fun, ArgTypes, BodyType, NewState5), - ?debug("done adding stuff for ~w\n", + ?debug("done adding stuff for ~tw\n", [state__lookup_name(get_label(Fun), State)]), analyze_loop(NewState6) end @@ -494,23 +487,23 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], end, ?debug("--------------------------------------------------------\n", []), - ?debug("Fun: ~p\n", [state__lookup_name(Fun, State)]), + ?debug("Fun: ~tp\n", [state__lookup_name(Fun, State)]), ?debug("Module ~p\n", [State#state.module]), - ?debug("CArgs ~s\n", [erl_types:t_to_string(t_product(CArgs))]), - ?debug("ArgTypes ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]), - ?debug("BifArgs ~p\n", [erl_types:t_to_string(t_product(BifArgs))]), + ?debug("CArgs ~ts\n", [erl_types:t_to_string(t_product(CArgs))]), + ?debug("ArgTypes ~ts\n", [erl_types:t_to_string(t_product(ArgTypes))]), + ?debug("BifArgs ~tp\n", [erl_types:t_to_string(t_product(BifArgs))]), NewArgsSig = t_inf_lists(SigArgs, ArgTypes, Opaques), - ?debug("SigArgs ~s\n", [erl_types:t_to_string(t_product(SigArgs))]), - ?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]), + ?debug("SigArgs ~ts\n", [erl_types:t_to_string(t_product(SigArgs))]), + ?debug("NewArgsSig: ~ts\n", [erl_types:t_to_string(t_product(NewArgsSig))]), NewArgsContract = t_inf_lists(CArgs, ArgTypes, Opaques), - ?debug("NewArgsContract: ~s\n", + ?debug("NewArgsContract: ~ts\n", [erl_types:t_to_string(t_product(NewArgsContract))]), NewArgsBif = t_inf_lists(BifArgs, ArgTypes, Opaques), - ?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]), + ?debug("NewArgsBif: ~ts\n", [erl_types:t_to_string(t_product(NewArgsBif))]), NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract), NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif, Opaques), - ?debug("NewArgTypes ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]), + ?debug("NewArgTypes ~ts\n", [erl_types:t_to_string(t_product(NewArgTypes))]), ?debug("\n", []), BifRet = BifRange(NewArgTypes), @@ -518,18 +511,18 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], RetWithoutContr = t_inf(SigRange, BifRet), RetWithoutLocal = t_inf(ContrRet, RetWithoutContr), - ?debug("RetWithoutContr: ~s\n",[erl_types:t_to_string(RetWithoutContr)]), - ?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]), - ?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]), - ?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]), - ?debug("ContrRet: ~s\n", [erl_types:t_to_string(ContrRet)]), - ?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]), + ?debug("RetWithoutContr: ~ts\n",[erl_types:t_to_string(RetWithoutContr)]), + ?debug("RetWithoutLocal: ~ts\n", [erl_types:t_to_string(RetWithoutLocal)]), + ?debug("BifRet: ~ts\n", [erl_types:t_to_string(BifRange(NewArgTypes))]), + ?debug("SigRange: ~ts\n", [erl_types:t_to_string(SigRange)]), + ?debug("ContrRet: ~ts\n", [erl_types:t_to_string(ContrRet)]), + ?debug("LocalRet: ~ts\n", [erl_types:t_to_string(LocalRet)]), State1 = case is_race_analysis_enabled(State) of true -> Ann = cerl:get_ann(Tree), - File = get_file(Ann), + File = get_file(Ann, State), Line = abs(get_line(Ann)), dialyzer_races:store_race_call(Fun, ArgTypes, Args, {File, Line}, State); @@ -603,7 +596,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], false -> t_inf(RetWithoutLocal, LocalRet) end, NewAccRet = t_sup(AccRet, TotalRet), - ?debug("NewAccRet: ~s\n", [t_to_string(NewAccRet)]), + ?debug("NewAccRet: ~ts\n", [t_to_string(NewAccRet)]), {NewWarnings, State4} = state__remove_added_warnings(State, State3), {HowMany, OldWarnings} = Warns, NewWarns = @@ -978,12 +971,21 @@ handle_case(Tree, Map, State) -> false -> State1 end, Map2 = join_maps_begin(Map1), - {MapList, State3, Type} = + {MapList, State3, Type, Warns} = handle_clauses(Clauses, Arg, ArgType, ArgType, State2, - [], Map2, [], []), + [], Map2, [], [], []), + %% Non-Erlang BEAM languages, such as Elixir, expand language constructs + %% into case statements. In that case, we do not want to warn on + %% individual clauses not matching unless none of them can. + SupressForced = is_compiler_generated(cerl:get_ann(Tree)) + andalso not (t_is_none(Type)), + State4 = lists:foldl(fun({T,R,M,F}, S) -> + state__add_warning( + S,T,R,M,F andalso (not SupressForced)) + end, State3, Warns), Map3 = join_maps_end(MapList, Map2), debug_pp_map(Map3), - {State3, Map3, Type} + {State4, Map3, Type} end. %%---------------------------------------- @@ -1082,22 +1084,24 @@ handle_receive(Tree, Map, State) -> RaceListSize + 1, State); false -> State end, - {MapList, State2, ReceiveType} = + {MapList, State2, ReceiveType, Warns} = handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map, - [], []), + [], [], []), + State3 = lists:foldl(fun({T,R,M,F}, S) -> state__add_warning(S,T,R,M,F) end, + State2, Warns), Map1 = join_maps(MapList, Map), - {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2), - Opaques = State3#state.opaques, + {State4, Map2, TimeoutType} = traverse(Timeout, Map1, State3), + Opaques = State4#state.opaques, case (t_is_atom(TimeoutType, Opaques) andalso (t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of true -> - {State3, Map2, ReceiveType}; + {State4, Map2, ReceiveType}; false -> Action = cerl:receive_action(Tree), - {State4, Map3, ActionType} = traverse(Action, Map, State3), + {State5, Map3, ActionType} = traverse(Action, Map, State4), Map4 = join_maps([Map3, Map1], Map), Type = t_sup(ReceiveType, ActionType), - {State4, Map4, Type} + {State5, Map4, Type} end. %%---------------------------------------- @@ -1200,7 +1204,7 @@ handle_tuple(Tree, Map, State) -> TagVal = cerl:atom_val(Tag), case state__lookup_record(TagVal, length(Left), State1) of error -> {State1, Map1, TupleType}; - {ok, RecType} -> + {ok, RecType, FieldNames} -> InfTupleType = t_inf(RecType, TupleType), case t_is_none(InfTupleType) of true -> @@ -1221,13 +1225,23 @@ handle_tuple(Tree, Map, State) -> Tree, Msg), {State2, Map1, t_none()}; {error, opaque, ErrorPat, ErrorType, OpaqueType} -> + OpaqueStr = format_type(OpaqueType, State1), + Name = field_name(Elements, ErrorPat, FieldNames), Msg = {opaque_match, - [format_patterns(ErrorPat), - format_type(ErrorType, State1), - format_type(OpaqueType, State1)]}, + ["record field" ++ Name ++ + " declared to be of type " ++ + format_type(ErrorType, State1), + OpaqueStr, OpaqueStr]}, State2 = state__add_warning(State1, ?WARN_OPAQUE, Tree, Msg), {State2, Map1, t_none()}; + {error, record, ErrorPat, ErrorType, _} -> + Msg = {record_match, + [format_patterns(ErrorPat), + format_type(ErrorType, State1)]}, + State2 = state__add_warning(State1, ?WARN_MATCHING, + Tree, Msg), + {State2, Map1, t_none()}; {Map2, ETypes} -> {State1, Map2, t_tuple(ETypes)} end @@ -1241,11 +1255,20 @@ handle_tuple(Tree, Map, State) -> end end. +field_name(Elements, ErrorPat, FieldNames) -> + try + [Pat] = ErrorPat, + Take = lists:takewhile(fun(X) -> X =/= Pat end, Elements), + " " ++ format_atom(lists:nth(length(Take), FieldNames)) + catch + _:_ -> "" + end. + %%---------------------------------------- %% Clauses %% handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, - Acc, ClauseAcc) -> + Acc, ClauseAcc, WarnAcc0) -> IsRaceAnalysisEnabled = is_race_analysis_enabled(State), State1 = case IsRaceAnalysisEnabled of @@ -1258,8 +1281,8 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, State); false -> State end, - {State2, ClauseMap, BodyType, NewArgType} = - do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1), + {State2, ClauseMap, BodyType, NewArgType, WarnAcc} = + do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1, WarnAcc0), {NewClauseAcc, State3} = case IsRaceAnalysisEnabled of true -> @@ -1277,9 +1300,9 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, false -> {[BodyType|CaseTypes], [ClauseMap|Acc]} end, handle_clauses(Left, Arg, NewArgType, OrigArgType, State3, - NewCaseTypes, MapIn, NewAcc, NewClauseAcc); + NewCaseTypes, MapIn, NewAcc, NewClauseAcc, WarnAcc); handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, - ClauseAcc) -> + ClauseAcc, WarnAcc) -> State1 = case is_race_analysis_enabled(State) of true -> @@ -1289,9 +1312,9 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, RaceListSize + 1, State); false -> State end, - {lists:reverse(Acc), State1, t_sup(CaseTypes)}. + {lists:reverse(Acc), State1, t_sup(CaseTypes), WarnAcc}. -do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> +do_clause(C, Arg, ArgType0, OrigArgType, Map, State, Warns) -> Pats = cerl:clause_pats(C), Guard = cerl:clause_guard(C), Body = cerl:clause_body(C), @@ -1319,17 +1342,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> end, case BindRes of {error, ErrorType, NewPats, Type, OpaqueTerm} -> - ?debug("Failed binding pattern: ~s\nto ~s\n", + ?debug("Failed binding pattern: ~ts\nto ~ts\n", [cerl_prettypr:format(C), format_type(ArgType0, State1)]), case state__warning_mode(State1) of false -> - {State1, Map, t_none(), ArgType0}; + {State1, Map, t_none(), ArgType0, Warns}; true -> {Msg, Force} = case t_is_none(ArgType0) of true -> - PatString = format_patterns(Pats), - PatTypes = [PatString, format_type(OrigArgType, State1)], %% See if this is covered by an earlier clause or if it %% simply cannot match OrigArgTypes = @@ -1337,17 +1358,27 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> true -> Any = t_any(), [Any || _ <- Pats]; false -> t_to_tlist(OrigArgType) end, + PatString = format_patterns(Pats), + ArgTypeString = format_type(OrigArgType, State1), + BindResOrig = + bind_pat_vars(Pats, OrigArgTypes, [], Map1, State1), Tag = - case bind_pat_vars(Pats, OrigArgTypes, [], Map1, State1) of + case BindResOrig of {error, bind, _, _, _} -> pattern_match; {error, record, _, _, _} -> record_match; {error, opaque, _, _, _} -> opaque_match; {_, _} -> pattern_match_cov end, - {{Tag, PatTypes}, false}; + PatTypes = case BindResOrig of + {error, opaque, _, _, OpaqueType} -> + [PatString, ArgTypeString, + format_type(OpaqueType, State1)]; + _ -> [PatString, ArgTypeString] + end, + {{Tag, PatTypes}, false}; false -> %% Try to find out if this is a default clause in a list - %% comprehension and supress this. A real Hack(tm) + %% comprehension and suppress this. A real Hack(tm) Force0 = case is_compiler_generated(cerl:get_ann(C)) of true -> @@ -1403,8 +1434,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {record_match, _} -> ?WARN_MATCHING; {pattern_match_cov, _} -> ?WARN_MATCHING end, - {state__add_warning(State1, WarnType, C, Msg, Force), - Map, t_none(), ArgType0} + {State1, Map, t_none(), ArgType0, [{WarnType, C, Msg, Force}|Warns]} end; {Map2, PatTypes} -> Map3 = @@ -1428,7 +1458,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> end, case bind_guard(Guard, Map3, State1) of {error, Reason} -> - ?debug("Failed guard: ~s\n", + ?debug("Failed guard: ~ts\n", [cerl_prettypr:format(C, [{hook, cerl_typean:pp_hook()}])]), PatString = format_patterns(Pats), DefaultMsg = @@ -1437,9 +1467,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> false -> {guard_fail_pat, [PatString, format_type(ArgType0, State1)]} end, - State2 = + Warn = case Reason of - none -> state__add_warning(State1, ?WARN_MATCHING, C, DefaultMsg); + none -> {?WARN_MATCHING, C, DefaultMsg, false}; {FailGuard, Msg} -> case is_compiler_generated(cerl:get_ann(FailGuard)) of false -> @@ -1448,15 +1478,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {neg_guard_fail, _} -> ?WARN_MATCHING; {opaque_guard, _} -> ?WARN_OPAQUE end, - state__add_warning(State1, WarnType, FailGuard, Msg); + {WarnType, FailGuard, Msg, false}; true -> - state__add_warning(State1, ?WARN_MATCHING, C, Msg) + {?WARN_MATCHING, C, Msg, false} end end, - {State2, Map, t_none(), NewArgType}; + {State1, Map, t_none(), NewArgType, [Warn|Warns]}; Map4 -> {RetState, RetMap, BodyType} = traverse(Body, Map4, State1), - {RetState, RetMap, BodyType, NewArgType} + {RetState, RetMap, BodyType, NewArgType, Warns} end end. @@ -1509,7 +1539,7 @@ bind_pat_vars_reverse(Pats, Types, Acc, Map, State) -> end. bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> - ?debug("Binding pat: ~w to ~s\n", [cerl:type(Pat), format_type(Type, State)] + ?debug("Binding pat: ~tw to ~ts\n", [cerl:type(Pat), format_type(Type, State)] ), Opaques = State#state.opaques, {NewMap, TypeOut} = @@ -1622,7 +1652,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> TagAtom = cerl:atom_val(Tag), case state__lookup_record(TagAtom, length(Left), State) of error -> {false, t_tuple(length(Es))}; - {ok, Record} -> + {ok, Record, _FieldNames} -> [_Head|AnyTail] = [t_any() || _ <- Es], UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]), {not t_is_equal(Record, UntypedRecord), Record} @@ -1807,7 +1837,7 @@ bind_guard(Guard, Map, State) -> end. bind_guard(Guard, Map, Env, Eval, State) -> - ?debug("Handling ~w guard: ~s\n", + ?debug("Handling ~tw guard: ~ts\n", [Eval, cerl_prettypr:format(Guard, [{noann, true}])]), case cerl:type(Guard) of binary -> @@ -1986,7 +2016,7 @@ handle_guard_type_test(Guard, F, Map, Env, Eval, State) -> ?debug("Type test: ~w failed\n", [F]), signal_guard_fail(Eval, Guard, [ArgType], State); {ok, NewArgType, Ret} -> - ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n", + ?debug("Type test: ~w succeeded, NewType: ~ts, Ret: ~ts\n", [F, t_to_string(NewArgType), t_to_string(Ret)]), {enter_type(Arg, NewArgType, Map1), Ret} end. @@ -2150,7 +2180,7 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) -> TupleType = case state__lookup_record(Tag, ArityMin1, State) of error -> Tuple; - {ok, Prototype} -> Prototype + {ok, Prototype, _FieldNames} -> Prototype end, Type = t_inf(TupleType, RecType, State#state.opaques), case t_is_none(Type) of @@ -2272,8 +2302,8 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) -> bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), {Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State), - ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1), - t_to_string(Type2)]), + ?debug("Types are:~ts =:= ~ts\n", [t_to_string(Type1), + t_to_string(Type2)]), Opaques = State#state.opaques, Inf = t_inf(Type1, Type2, Opaques), case t_is_none(Inf) of @@ -2600,7 +2630,7 @@ bind_guard_case_clauses(Arg, Clauses, Map0, Env, Eval, State) -> 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). + t_none(), [], [], State). filter_fail_clauses([Clause|Left]) -> case (cerl:clause_pats(Clause) =:= []) of @@ -2619,7 +2649,7 @@ filter_fail_clauses([]) -> []. bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], - Map, Env, Eval, AccType, AccMaps, State) -> + Map, Env, Eval, AccType, AccMaps, Throws, State) -> Pats = cerl:clause_pats(Clause), {NewMap0, ArgType} = case Pats of @@ -2663,9 +2693,9 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], case (NewMap1 =:= none) orelse t_is_none(GenArgType) of true -> bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, - Eval, AccType, AccMaps, State); + Eval, AccType, AccMaps, Throws, State); false -> - {NewAccType, NewAccMaps} = + {NewAccType, NewAccMaps, NewThrows} = try {NewMap2, GuardType} = bind_guard(Guard, NewMap1, Env, pos, State), case t_is_none(t_inf(t_atom(true), GuardType)) of @@ -2689,17 +2719,26 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], dont_know -> ok end, - {t_sup(AccType, CType), [NewMap3|AccMaps]} + {t_sup(AccType, CType), [NewMap3|AccMaps], Throws} catch - throw:{fail, _What} -> {AccType, AccMaps} + throw:{fail, Reason} -> + Throws1 = case Reason of + none -> Throws; + _ -> Throws ++ [Reason] + end, + {AccType, AccMaps, Throws1} end, bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, - Eval, NewAccType, NewAccMaps, State) + Eval, NewAccType, NewAccMaps, NewThrows, State) end; bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, - AccType, AccMaps, _State) -> + AccType, AccMaps, Throws, _State) -> case t_is_none(AccType) of - true -> throw({fail, none}); + true -> + case Throws of + [Throw|_] -> throw({fail, Throw}); + [] -> throw({fail, none}) + end; false -> {join_maps_end(AccMaps, Map), AccType} end. @@ -2808,7 +2847,7 @@ enter_type(Key, Val, MS) -> ?debug("Binding ~p to ~p\n", [KeyLabel, NewKey]), enter_type(NewKey, Val, MS); error -> - ?debug("Entering ~p :: ~s\n", [KeyLabel, t_to_string(Val)]), + ?debug("Entering ~p :: ~ts\n", [KeyLabel, t_to_string(Val)]), case maps:find(KeyLabel, Map) of {ok, Value} -> case erl_types:t_is_equal(Val, Value) of @@ -2908,7 +2947,7 @@ debug_pp_map(#map{map = Map}=MapRec) -> Keys = maps:keys(Map), io:format("Map:\n", []), lists:foreach(fun (Key) -> - io:format("\t~w :: ~s\n", + io:format("\t~w :: ~ts\n", [Key, t_to_string(lookup_type(Key, MapRec))]) end, Keys), ok. @@ -3059,21 +3098,23 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, Ann = cerl:get_ann(Tree), case Force of true -> - WarningInfo = {get_file(Ann), + WarningInfo = {get_file(Ann, State), abs(get_line(Ann)), State#state.curr_fun}, Warn = {Tag, WarningInfo, Msg}, - ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]), + ?debug("MSG ~ts\n", [dialyzer:format_warning(Warn)]), State#state{warnings = [Warn|Warnings]}; false -> case is_compiler_generated(Ann) of true -> State; false -> - WarningInfo = {get_file(Ann), get_line(Ann), State#state.curr_fun}, + WarningInfo = {get_file(Ann, State), + get_line(Ann), + State#state.curr_fun}, Warn = {Tag, WarningInfo, Msg}, case Tag of ?WARN_CONTRACT_RANGE -> ok; - _ -> ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]) + _ -> ?debug("MSG ~ts\n", [dialyzer:format_warning(Warn)]) end, State#state{warnings = [Warn|Warnings]} end @@ -3082,7 +3123,10 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, state__remove_added_warnings(OldState, NewState) -> #state{warnings = OldWarnings} = OldState, #state{warnings = NewWarnings} = NewState, - {NewWarnings -- OldWarnings, NewState#state{warnings = OldWarnings}}. + case NewWarnings =:= OldWarnings of + true -> {[], NewState}; + false -> {NewWarnings -- OldWarnings, NewState#state{warnings = OldWarnings}} + end. state__add_warnings(Warns, #state{warnings = Warnings} = State) -> State#state{warnings = Warns ++ Warnings}. @@ -3197,7 +3241,8 @@ state__lookup_record(Tag, Arity, #state{records = Records}) -> RecType = t_tuple([t_atom(Tag)| [FieldType || {_FieldName, _Abstr, FieldType} <- Fields]]), - {ok, RecType}; + FieldNames = [FieldName || {FieldName, _Abstr, _FieldType} <- Fields], + {ok, RecType, FieldNames}; error -> error end. @@ -3282,7 +3327,9 @@ state__clean_not_called(#state{fun_tab = FunTab} = State) -> 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). + List = [{Fun, t_fun(Args, Ret)} || + {Fun, {Args, Ret}} <- dict:to_list(Tab1)], + orddict:from_list(List). state__fun_type(Fun, #state{fun_tab = FunTab}) -> Label = @@ -3316,14 +3363,14 @@ state__update_fun_entry(Tree, ArgTypes, Out0, SameOut = t_is_equal(OldOut, Out), if SameArgs, SameOut -> - ?debug("Fixpoint for ~w: ~s\n", + ?debug("Fixpoint for ~tw: ~ts\n", [state__lookup_name(Fun, State), t_to_string(t_fun(ArgTypes, Out))]), State; true -> %% Can only happen in self-recursive functions. NewEntry = {OldArgTypes, Out}, - ?debug("New Entry for ~w: ~s\n", + ?debug("New Entry for ~tw: ~ts\n", [state__lookup_name(Fun, State), t_to_string(t_fun(OldArgTypes, Out))]), NewFunTab = dict:store(Fun, NewEntry, FunTab), @@ -3345,7 +3392,7 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph, || MFA <- MFAList], %% Must filter the result for results in this module. FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)], - ?debug("~w: Will try to add:~w\n", + ?debug("~tw: Will try to add:~tw\n", [state__lookup_name(Label, State), MFAList]), lists:foldl(fun(L, AccState) -> state__add_work(L, AccState) @@ -3392,25 +3439,25 @@ state__fun_info(Fun, #state{callgraph = CG, fun_tab = FunTab, plt = PLT}) -> {not_handled, {_Args, Ret}} -> Ret; {_Args, Ret} -> Ret end, - ?debug("LocalRet: ~s\n", [t_to_string(LocalRet)]), + ?debug("LocalRet: ~ts\n", [t_to_string(LocalRet)]), {Fun, Sig, Contract, LocalRet}. forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) -> - {OldArgTypes, OldOut, Fixpoint} = + {NewArgTypes, OldOut, Fixpoint} = case dict:find(Fun, FunTab) of - {ok, {not_handled, {OldArgTypes0, OldOut0}}} -> - {OldArgTypes0, OldOut0, false}; + {ok, {not_handled, {_OldArgTypesAreNone, OldOut0}}} -> + {ArgTypes, OldOut0, false}; {ok, {OldArgTypes0, OldOut0}} -> - {OldArgTypes0, OldOut0, - t_is_subtype(t_product(ArgTypes), t_product(OldArgTypes0))} + NewArgTypes0 = [t_sup(X, Y) || + {X, Y} <- lists:zip(ArgTypes, OldArgTypes0)], + {NewArgTypes0, OldOut0, + t_is_equal(t_product(NewArgTypes0), t_product(OldArgTypes0))} end, case Fixpoint of true -> State; false -> - NewArgTypes = [t_sup(X, Y) || - {X, Y} <- lists:zip(ArgTypes, OldArgTypes)], NewWork = add_work(Fun, Work), - ?debug("~w: forwarding args ~s\n", + ?debug("~tw: forwarding args ~ts\n", [state__lookup_name(Fun, State), t_to_string(t_product(NewArgTypes))]), NewFunTab = dict:store(Fun, {NewArgTypes, OldOut}, FunTab), @@ -3467,6 +3514,12 @@ state__put_races(Races, State) -> state__records_only(#state{records = Records}) -> #state{records = Records}. +-spec state__translate_file(file:filename(), state()) -> file:filename(). + +state__translate_file(FakeFile, State) -> + #state{codeserver = CodeServer, module = Module} = State, + dialyzer_codeserver:translate_fake_file(CodeServer, Module, FakeFile). + %%% =========================================================================== %%% %%% Races @@ -3538,9 +3591,11 @@ get_line([Line|_]) when is_integer(Line) -> Line; get_line([_|Tail]) -> get_line(Tail); get_line([]) -> -1. -get_file([]) -> []; -get_file([{file, File}|_]) -> File; -get_file([_|Tail]) -> get_file(Tail). +get_file([], _State) -> []; +get_file([{file, FakeFile}|_], State) -> + state__translate_file(FakeFile, State); +get_file([_|Tail], State) -> + get_file(Tail, State). is_compiler_generated(Ann) -> lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1). @@ -3575,6 +3630,7 @@ format_arg(Arg) -> case cerl:var_name(Arg) of Atom when is_atom(Atom) -> case atom_to_list(Atom) of + "@"++_ -> Default; "cor"++_ -> Default; "rec"++_ -> Default; Name -> Name ++ "::" @@ -3635,6 +3691,7 @@ map_pats(Pats) -> case cerl:var_name(Tree) of Atom when is_atom(Atom) -> case atom_to_list(Atom) of + "@"++_ -> cerl:c_var(''); "cor"++_ -> cerl:c_var(''); "rec"++_ -> cerl:c_var(''); _ -> cerl:set_ann(Tree, []) @@ -3650,6 +3707,9 @@ map_pats(Pats) -> fold_literals(TreeList) -> [cerl:fold_literal(Tree) || Tree <- TreeList]. +format_atom(A) -> + format_cerl(cerl:c_atom(A)). + type(Tree) -> Folded = cerl:fold_literal(Tree), case cerl:type(Folded) of diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl index 273c05c54c..36cdc0876c 100644 --- a/lib/dialyzer/src/dialyzer_dep.erl +++ b/lib/dialyzer/src/dialyzer_dep.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_dep.erl diff --git a/lib/dialyzer/src/dialyzer_explanation.erl b/lib/dialyzer/src/dialyzer_explanation.erl index 968f8df78e..10b3ef8ea5 100644 --- a/lib/dialyzer/src/dialyzer_explanation.erl +++ b/lib/dialyzer/src/dialyzer_explanation.erl @@ -1,9 +1,5 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009-2016. All Rights Reserved. -%% +%% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_explanation.erl @@ -50,4 +43,3 @@ expl_loop(Parent, CServer, Plt) -> send_explanation(Parent, Expl) -> Parent ! {self(), explanation, Expl}, ok. - diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index 30d2bdeca4..b4b1872c12 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%------------------------------------------------------------------------ -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%----------------------------------------------------------------------- %%% File : dialyzer_gui_wx.erl @@ -310,7 +303,7 @@ createWarningsMenu() -> addCheckedItem(WarningsMenu, ?menuID_WARN_FAIL_FUN_CALLS, "Failing function calls"), addCheckedItem(WarningsMenu, ?menuID_WARN_BAD_FUN, "Bad fun applications"), - addCheckedItem(WarningsMenu, ?menuID_WARN_OPAQUE, "Opaqueness violations"), + addCheckedItem(WarningsMenu, ?menuID_WARN_OPAQUE, "Opacity violations"), addCheckedItem(WarningsMenu, ?menuID_WARN_LIST_CONSTR, "Improper list constructions"), addCheckedItem(WarningsMenu, ?menuID_WARN_UNUSED_FUN, "Unused functions"), @@ -476,18 +469,18 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, Msg = io_lib:format("The following functions are called " "but type information about them is not available.\n" "The analysis might get more precise by including " - "the modules containing these functions:\n\n\t~p\n", + "the modules containing these functions:\n\n\t~tp\n", [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, + Map = fun({M,F,A}) -> io_lib:format("~tp:~tp/~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]), + "that they are exported:\n~ts\n", [ExtTypeString]), free_editor(State, "Analysis done", Msg), gui_loop(State); {BackendPid, log, LogMsg} -> @@ -505,8 +498,9 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, end, ExplanationPid = spawn_link(Fun), gui_loop(State#gui_state{expl_pid = ExplanationPid}); - {BackendPid, done, _NewPlt, NewDocPlt} -> + {BackendPid, done, NewPlt, NewDocPlt} -> message(State, "Analysis done"), + dialyzer_plt:delete(NewPlt), config_gui_stop(State), gui_loop(State#gui_state{doc_plt = NewDocPlt}); {'EXIT', BackendPid, {error, Reason}} -> @@ -514,7 +508,7 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, config_gui_stop(State), gui_loop(State); {'EXIT', BackendPid, Reason} when Reason =/= 'normal' -> - free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~p", [Reason])), + free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~tp", [Reason])), config_gui_stop(State), gui_loop(State) end. @@ -932,7 +926,7 @@ include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> wxButton:connect(DeleteAllButton, command_button_clicked), wxButton:connect(Ok, command_button_clicked), wxButton:connect(Cancel, command_button_clicked), - Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs], + Dirs = [io_lib:format("~ts", [X]) || X <- Options#options.include_dirs], wxListBox:set(Box, Dirs), Layout = wxBoxSizer:new(?wxVERTICAL), Buttons = wxBoxSizer:new(?wxHORIZONTAL), @@ -1099,7 +1093,7 @@ macro_loop(Options, Win, Box, MacroText, TermText, Frame) -> Fun = fun(X) -> Val = wxControlWithItems:getString(Box,X), - [MacroName|_] = re:split(Val, " ", [{return, list}]), + [MacroName|_] = re:split(Val, " ", [{return, list}, unicode]), list_to_atom(MacroName) end, Delete = [Fun(X) || X <- List], @@ -1124,13 +1118,13 @@ handle_help(State, Title, Txt) -> case file:open(FileName, [read]) of {error, Reason} -> error_sms(State, - io_lib:format("Could not find doc/~s file!\n\n ~p", + io_lib:format("Could not find doc/~ts file!\n\n ~tp", [Txt, Reason])); {ok, _Handle} -> case file:read_file(FileName) of {error, Reason} -> error_sms(State, - io_lib:format("Could not read doc/~s file!\n\n ~p", + io_lib:format("Could not read doc/~ts file!\n\n ~tp", [Txt, Reason])); {ok, Binary} -> Contents = binary_to_list(Binary), @@ -1229,7 +1223,7 @@ update_explanation(#gui_state{explanation_box = Box}, Explanation) -> wxTextCtrl:appendText(Box, ExplString). format_explanation({function_return, {M, F, A}, NewList}) -> - io_lib:format("The function ~p: ~p/~p returns ~p\n", + io_lib:format("The function ~w:~tw/~w returns ~ts\n", [M, F, A, erl_types:t_to_string(NewList)]); format_explanation(Explanation) -> io_lib:format("~p\n", [Explanation]). diff --git a/lib/dialyzer/src/dialyzer_gui_wx.hrl b/lib/dialyzer/src/dialyzer_gui_wx.hrl index e81eeb1ab5..fe763a9abc 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.hrl +++ b/lib/dialyzer/src/dialyzer_gui_wx.hrl @@ -1,3 +1,15 @@ +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + -include_lib("wx/include/wx.hrl"). diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index add660eae9..03040f8e38 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,16 +12,9 @@ %% See the License for the specific language governing permissions and %% limitations under the License. %% -%% %CopyrightEnd% -%% - -%%%---------------------------------------------------------------------- -%%% File : dialyzer_options.erl -%%% Authors : Richard Carlsson <[email protected]> -%%% Description : Provides a better way to start Dialyzer from a script. -%%% -%%% Created : 17 Oct 2004 by Richard Carlsson <[email protected]> -%%%---------------------------------------------------------------------- +%% @copyright 2004 Richard Carlsson +%% @author Richard Carlsson <[email protected]> +%% @doc Provides a better way to start Dialyzer from a script. -module(dialyzer_options). @@ -123,7 +112,7 @@ adapt_get_warnings(Opts = #options{analysis_type = Mode, -spec bad_option(string(), term()) -> no_return(). bad_option(String, Term) -> - Msg = io_lib:format("~s: ~P", [String, Term, 25]), + Msg = io_lib:format("~ts: ~tP", [String, Term, 25]), throw({dialyzer_options_error, lists:flatten(Msg)}). build_options([{OptName, undefined}|Rest], Options) when is_atom(OptName) -> @@ -288,6 +277,9 @@ assert_solvers([Term|_]) -> -spec build_warnings([atom()], dial_warn_tags()) -> dial_warn_tags(). +%% The warning options are checked by the code linter. +%% The function erl_lint:is_module_dialyzer_option/1 must +%% be updated if options are added or removed. build_warnings([Opt|Opts], Warnings) -> NewWarnings = case Opt of diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index cf2f0e919e..95c8b5ebce 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%---------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_plt.erl @@ -38,15 +31,15 @@ included_files/1, from_file/1, get_default_plt/0, - get_types/1, + get_module_types/2, get_exported_types/1, - %% insert/3, insert_list/2, insert_contract_list/2, insert_callbacks/2, insert_types/2, insert_exported_types/2, lookup/2, + is_contract/2, lookup_contract/2, lookup_callbacks/2, lookup_module/2, @@ -57,8 +50,7 @@ get_specs/1, get_specs/4, to_file/4, - get_mini_plt/1, - restore_full_plt/2 + delete/1 ]). %% Debug utilities @@ -66,6 +58,8 @@ -export_type([plt/0, plt_info/0]). +-include_lib("stdlib/include/ms_transform.hrl"). + %%---------------------------------------------------------------------- -type mod_deps() :: dialyzer_callgraph:mod_deps(). @@ -81,18 +75,16 @@ %%---------------------------------------------------------------------- --record(plt, {info = table_new() :: dict:dict(), - types = table_new() :: dict:dict(), - contracts = table_new() :: dict:dict(), - callbacks = table_new() :: dict:dict(), - exported_types = sets:new() :: sets:set()}). - --record(mini_plt, {info :: ets:tid(), - contracts :: ets:tid(), - callbacks :: ets:tid() - }). +-record(plt, {info :: ets:tid(), %% {mfa() | integer(), ret_args_types()} + types :: ets:tid(), %% {module(), erl_types:type_table()} + contracts :: ets:tid(), %% {mfa(), #contract{}} + callbacks :: ets:tid(), %% {module(), + %% [{mfa(), + %% dialyzer_contracts:file_contract()}] + exported_types :: ets:tid() %% {module(), sets:set()} + }). --opaque plt() :: #plt{} | #mini_plt{}. +-opaque plt() :: #plt{}. -include("dialyzer.hrl"). @@ -114,7 +106,17 @@ -spec new() -> plt(). new() -> - #plt{}. + [ETSInfo, ETSContracts] = + [ets:new(Name, [public]) || + Name <- [plt_info, plt_contracts]], + [ETSTypes, ETSCallbacks, ETSExpTypes] = + [ets:new(Name, [compressed, public]) || + Name <- [plt_types, plt_callbacks, plt_exported_types]], + #plt{info = ETSInfo, + types = ETSTypes, + contracts = ETSContracts, + callbacks = ETSCallbacks, + exported_types = ETSExpTypes}. -spec delete_module(plt(), atom()) -> plt(). @@ -125,50 +127,55 @@ delete_module(#plt{info = Info, types = Types, #plt{info = table_delete_module(Info, Mod), types = table_delete_module2(Types, Mod), contracts = table_delete_module(Contracts, Mod), - callbacks = table_delete_module(Callbacks, Mod), + callbacks = table_delete_module2(Callbacks, Mod), exported_types = table_delete_module1(ExpTypes, Mod)}. -spec delete_list(plt(), [mfa() | integer()]) -> plt(). -delete_list(#plt{info = Info, types = Types, - contracts = Contracts, - callbacks = Callbacks, - exported_types = ExpTypes}, List) -> - #plt{info = table_delete_list(Info, List), - types = Types, - contracts = table_delete_list(Contracts, List), - callbacks = Callbacks, - exported_types = ExpTypes}. +delete_list(#plt{info = Info, + contracts = Contracts}=Plt, List) -> + Plt#plt{info = ets_table_delete_list(Info, List), + contracts = ets_table_delete_list(Contracts, List)}. -spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt(). -insert_contract_list(#mini_plt{contracts = Contracts} = PLT, List) -> +insert_contract_list(#plt{contracts = Contracts} = PLT, List) -> true = ets:insert(Contracts, List), PLT. -spec insert_callbacks(plt(), dialyzer_codeserver:codeserver()) -> plt(). insert_callbacks(#plt{callbacks = Callbacks} = Plt, Codeserver) -> - List = dialyzer_codeserver:get_callbacks(Codeserver), - Plt#plt{callbacks = table_insert_list(Callbacks, List)}. + CallbacksList = dialyzer_codeserver:get_callbacks(Codeserver), + CallbacksByModule = + [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} || + M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])], + true = ets:insert(Callbacks, CallbacksByModule), + Plt. + +-spec is_contract(plt(), mfa()) -> boolean(). + +is_contract(#plt{contracts = ETSContracts}, + {M, F, _} = MFA) when is_atom(M), is_atom(F) -> + ets:member(ETSContracts, MFA). -spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}. -lookup_contract(#mini_plt{contracts = ETSContracts}, +lookup_contract(#plt{contracts = ETSContracts}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> ets_table_lookup(ETSContracts, MFA). -spec lookup_callbacks(plt(), module()) -> 'none' | {'value', [{mfa(), dialyzer_contracts:file_contract()}]}. -lookup_callbacks(#mini_plt{callbacks = ETSCallbacks}, Mod) when is_atom(Mod) -> +lookup_callbacks(#plt{callbacks = ETSCallbacks}, Mod) when is_atom(Mod) -> ets_table_lookup(ETSCallbacks, Mod). -type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}. -spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt(). -insert_list(#mini_plt{info = Info} = PLT, List) -> +insert_list(#plt{info = Info} = PLT, List) -> true = ets:insert(Info, List), PLT. @@ -180,28 +187,31 @@ lookup(Plt, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> lookup(Plt, Label) when is_integer(Label) -> lookup_1(Plt, Label). -lookup_1(#mini_plt{info = Info}, MFAorLabel) -> +lookup_1(#plt{info = Info}, MFAorLabel) -> ets_table_lookup(Info, MFAorLabel). --spec insert_types(plt(), dict:dict()) -> plt(). +-spec insert_types(plt(), ets:tid()) -> plt(). -insert_types(PLT, Rec) -> - PLT#plt{types = Rec}. +insert_types(PLT, Records) -> + ok = dialyzer_utils:ets_move(Records, PLT#plt.types), + PLT. --spec insert_exported_types(plt(), sets:set()) -> plt(). +-spec insert_exported_types(plt(), ets:tid()) -> plt(). -insert_exported_types(PLT, Set) -> - PLT#plt{exported_types = Set}. +insert_exported_types(PLT, ExpTypes) -> + ok = dialyzer_utils:ets_move(ExpTypes, PLT#plt.exported_types), + PLT. --spec get_types(plt()) -> dict:dict(). +-spec get_module_types(plt(), atom()) -> + 'none' | {'value', erl_types:type_table()}. -get_types(#plt{types = Types}) -> - Types. +get_module_types(#plt{types = Types}, M) when is_atom(M) -> + ets_table_lookup(Types, M). -spec get_exported_types(plt()) -> sets:set(). -get_exported_types(#plt{exported_types = ExpTypes}) -> - ExpTypes. +get_exported_types(#plt{exported_types = ETSExpTypes}) -> + sets:from_list([E || {E} <- table_to_list(ETSExpTypes)]). -type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}. @@ -218,20 +228,15 @@ all_modules(#plt{info = Info, contracts = Cs}) -> -spec contains_mfa(plt(), mfa()) -> boolean(). contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) -> - (table_lookup(Info, MFA) =/= none) - orelse (table_lookup(Contracts, MFA) =/= none). + ets:member(Info, MFA) orelse ets:member(Contracts, MFA). -spec get_default_plt() -> file:filename(). get_default_plt() -> case os:getenv("DIALYZER_PLT") of false -> - case os:getenv("HOME") of - false -> - plt_error("The HOME environment variable needs to be set " ++ - "so that Dialyzer knows where to find the default PLT"); - HomeDir -> filename:join(HomeDir, ".dialyzer_plt") - end; + {ok,[[HomeDir]]} = init:get_argument(home), + filename:join(HomeDir, ".dialyzer_plt"); UserSpecPlt -> UserSpecPlt end. @@ -246,30 +251,60 @@ from_file(FileName) -> from_file(FileName, false). from_file(FileName, ReturnInfo) -> + Plt = new(), + Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end, + case subproc(Fun) of + {ok, Return} -> + Return; + {error, Msg} -> + delete(Plt), + plt_error(Msg) + end. + +from_file1(Plt, FileName, ReturnInfo) -> case get_record_from_file(FileName) of {ok, Rec} -> case check_version(Rec) of error -> - Msg = io_lib:format("Old PLT file ~s\n", [FileName]), - plt_error(Msg); + Msg = io_lib:format("Old PLT file ~ts\n", [FileName]), + {error, Msg}; ok -> - Plt = #plt{info = Rec#file_plt.info, - types = Rec#file_plt.types, - contracts = Rec#file_plt.contracts, - callbacks = Rec#file_plt.callbacks, - exported_types = Rec#file_plt.exported_types}, + #file_plt{info = FileInfo, + contracts = FileContracts, + callbacks = FileCallbacks, + types = FileTypes, + exported_types = FileExpTypes} = Rec, + Types = [{Mod, maps:from_list(dict:to_list(Types))} || + {Mod, Types} <- dict:to_list(FileTypes)], + CallbacksList = dict:to_list(FileCallbacks), + CallbacksByModule = + [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} || + M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])], + #plt{info = ETSInfo, + types = ETSTypes, + contracts = ETSContracts, + callbacks = ETSCallbacks, + exported_types = ETSExpTypes} = Plt, + [true, true, true] = + [ets:insert(ETS, Data) || + {ETS, Data} <- [{ETSInfo, dict:to_list(FileInfo)}, + {ETSTypes, Types}, + {ETSContracts, dict:to_list(FileContracts)}]], + true = ets:insert(ETSCallbacks, CallbacksByModule), + true = ets:insert(ETSExpTypes, [{ET} || + ET <- sets:to_list(FileExpTypes)]), case ReturnInfo of - false -> Plt; + false -> {ok, Plt}; true -> PltInfo = {Rec#file_plt.file_md5_list, Rec#file_plt.mod_deps}, - {Plt, PltInfo} + {ok, {Plt, PltInfo}} end end; {error, Reason} -> - Msg = io_lib:format("Could not read PLT file ~s: ~p\n", + Msg = io_lib:format("Could not read PLT file ~ts: ~p\n", [FileName, Reason]), - plt_error(Msg) + {error, Msg} end. -type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'. @@ -278,6 +313,10 @@ from_file(FileName, ReturnInfo) -> | {'error', err_rsn()}. included_files(FileName) -> + Fun = fun() -> included_files1(FileName) end, + subproc(Fun). + +included_files1(FileName) -> case get_record_from_file(FileName) of {ok, #file_plt{file_md5_list = Md5}} -> {ok, [File || {File, _} <- Md5]}; @@ -310,6 +349,9 @@ get_record_from_file(FileName) -> -spec merge_plts([plt()]) -> plt(). +%% One of the PLTs of the list is augmented with the contents of the +%% other PLTs, and returned. The other PLTs are deleted. + merge_plts(List) -> {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} = group_fields(List), @@ -322,6 +364,12 @@ merge_plts(List) -> -spec merge_disj_plts([plt()]) -> plt(). +%% One of the PLTs of the list is augmented with the contents of the +%% other PLTs, and returned. The other PLTs are deleted. +%% +%% The keys are compared when checking for disjointness. Sometimes the +%% key is a module(), sometimes an mfa(). It boils down to checking if +%% any module occurs more than once. merge_disj_plts(List) -> {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} = group_fields(List), @@ -351,7 +399,7 @@ merge_plts_or_report_conflicts(PltFiles, Plts) -> 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]), + "PLTs:\n~tp\n", [ConfFiles]), plt_error(Msg) end. @@ -362,21 +410,42 @@ find_duplicates(List) -> -spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'. -to_file(FileName, - #plt{info = Info, types = Types, contracts = Contracts, - callbacks = Callbacks, exported_types = ExpTypes}, +%% Write the PLT to file, and deletes the PLT. +to_file(FileName, Plt, ModDeps, MD5_OldModDeps) -> + Fun = fun() -> to_file1(FileName, Plt, ModDeps, MD5_OldModDeps) end, + Return = subproc(Fun), + delete(Plt), + case Return of + ok -> ok; + Thrown -> throw(Thrown) + end. + +to_file1(FileName, + #plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts, + callbacks = ETSCallbacks, exported_types = ETSExpTypes}, ModDeps, {MD5, OldModDeps}) -> NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) end, OldModDeps, ModDeps), ImplMd5 = compute_implementation_md5(), + CallbacksList = + [Cb || + {_M, Cbs} <- tab2list(ETSCallbacks), + Cb <- Cbs], + Callbacks = dict:from_list(CallbacksList), + Info = dict:from_list(tab2list(ETSInfo)), + Types = tab2list(ETSTypes), + Contracts = dict:from_list(tab2list(ETSContracts)), + ExpTypes = sets:from_list([E || {E} <- tab2list(ETSExpTypes)]), + FileTypes = dict:from_list([{Mod, dict:from_list(maps:to_list(MTypes))} || + {Mod, MTypes} <- Types]), Record = #file_plt{version = ?VSN, file_md5_list = MD5, info = Info, contracts = Contracts, callbacks = Callbacks, - types = Types, + types = FileTypes, exported_types = ExpTypes, mod_deps = NewModDeps, implementation_md5 = ImplMd5}, @@ -384,9 +453,9 @@ to_file(FileName, case file:write_file(FileName, Bin) of ok -> ok; {error, Reason} -> - Msg = io_lib:format("Could not write PLT file ~s: ~w\n", + Msg = io_lib:format("Could not write PLT file ~ts: ~w\n", [FileName, Reason]), - throw({dialyzer_error, Msg}) + {dialyzer_error, Msg} end. -type md5_diff() :: [{'differ', atom()} | {'removed', atom()}]. @@ -399,6 +468,10 @@ to_file(FileName, | {'old_version', [file_md5()]}. check_plt(FileName, RemoveFiles, AddFiles) -> + Fun = fun() -> check_plt1(FileName, RemoveFiles, AddFiles) end, + subproc(Fun). + +check_plt1(FileName, RemoveFiles, AddFiles) -> case get_record_from_file(FileName) of {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> case check_version(Rec) of @@ -460,15 +533,14 @@ compute_md5_from_files(Files) -> compute_md5_from_file(File) -> case filelib:is_regular(File) of false -> - Msg = io_lib:format("Not a regular file: ~s\n", [File]), + Msg = io_lib:format("Not a regular file: ~ts\n", [File]), throw({dialyzer_error, Msg}); true -> - case dialyzer_utils:get_abstract_code_from_beam(File) of - error -> - Msg = io_lib:format("Could not get abstract code for file: ~s (please recompile it with +debug_info)\n", [File]), - throw({dialyzer_error, Msg}); - {ok, Abs} -> - erlang:md5(term_to_binary(Abs)) + case dialyzer_utils:get_core_from_beam(File) of + {error, Error} -> + throw({dialyzer_error, Error}); + {ok, Core} -> + erlang:md5(term_to_binary(Core)) end end. @@ -508,33 +580,29 @@ init_md5_list_1([], DiffList, Acc) -> init_md5_list_1(Md5List, [], Acc) -> {ok, lists:reverse(Acc, Md5List)}. --spec get_mini_plt(plt()) -> plt(). - -get_mini_plt(#plt{info = Info, contracts = Contracts, callbacks = Callbacks}) -> - [ETSInfo, ETSContracts, ETSCallbacks] = - [ets:new(Name, [public]) || Name <- [plt_info, plt_contracts, plt_callbacks]], - CallbackList = dict:to_list(Callbacks), - CallbacksByModule = - [{M, [Cb || {{M1,_,_},_} = Cb <- CallbackList, M1 =:= M]} || - M <- lists:usort([M || {{M,_,_},_} <- CallbackList])], - [true, true] = - [ets:insert(ETS, dict:to_list(Data)) || - {ETS, Data} <- [{ETSInfo, Info}, {ETSContracts, Contracts}]], - true = ets:insert(ETSCallbacks, CallbacksByModule), - #mini_plt{info = ETSInfo, contracts = ETSContracts, callbacks = ETSCallbacks}; -get_mini_plt(undefined) -> - undefined. - --spec restore_full_plt(plt(), plt()) -> plt(). - -restore_full_plt(#mini_plt{info = ETSInfo, contracts = ETSContracts}, Plt) -> - Info = dict:from_list(ets:tab2list(ETSInfo)), - Contracts = dict:from_list(ets:tab2list(ETSContracts)), - ets:delete(ETSContracts), - ets:delete(ETSInfo), - Plt#plt{info = Info, contracts = Contracts}; -restore_full_plt(undefined, undefined) -> - undefined. +-spec delete(plt()) -> 'ok'. + +delete(#plt{info = ETSInfo, + types = ETSTypes, + contracts = ETSContracts, + callbacks = ETSCallbacks, + exported_types = ETSExpTypes}) -> + true = ets:delete(ETSContracts), + true = ets:delete(ETSTypes), + true = ets:delete(ETSInfo), + true = ets:delete(ETSCallbacks), + true = ets:delete(ETSExpTypes), + ok. + +tab2list(Tab) -> + dialyzer_utils:ets_tab2list(Tab). + +subproc(Fun) -> + F = fun() -> exit(Fun()) end, + {Pid, Ref} = erlang:spawn_monitor(F), + receive {'DOWN', Ref, process, Pid, Return} -> + Return + end. %%--------------------------------------------------------------------------- %% Edoc @@ -543,7 +611,8 @@ restore_full_plt(undefined, undefined) -> get_specs(#plt{info = Info}) -> %% TODO: Should print contracts as well. - L = lists:sort([{MFA, Val} || {{_,_,_} = MFA, Val} <- table_to_list(Info)]), + L = lists:sort([{MFA, Val} || + {{_,_,_} = MFA, Val} <- table_to_list(Info)]), lists:flatten(create_specs(L, [])). beam_file_to_module(Filename) -> @@ -553,13 +622,13 @@ beam_file_to_module(Filename) -> get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> MFA = {M, F, A}, - case table_lookup(Info, MFA) of + case ets_table_lookup(Info, MFA) of none -> none; {value, Val} -> lists:flatten(create_specs([{MFA, Val}], [])) end. create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) -> - [io_lib:format("-spec ~w(~s) -> ~s\n", + [io_lib:format("-spec ~tw(~ts) -> ~ts\n", [F, expand_args(Args), erl_types:t_to_string(Ret)]) | create_specs(Left, M)]; create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) -> @@ -590,41 +659,30 @@ plt_error(Msg) -> %%--------------------------------------------------------------------------- %% Ets table -table_new() -> - dict:new(). - table_to_list(Plt) -> - dict:to_list(Plt). - -table_delete_module(Plt, Mod) -> - dict:filter(fun({M, _F, _A}, _Val) -> M =/= Mod; - (_, _) -> true - end, Plt). - -table_delete_module1(Plt, Mod) -> - sets:filter(fun({M, _F, _A}) -> M =/= Mod end, Plt). - -table_delete_module2(Plt, Mod) -> - dict:filter(fun(M, _Val) -> M =/= Mod end, Plt). - -table_delete_list(Plt, [H|T]) -> - table_delete_list(dict:erase(H, Plt), T); -table_delete_list(Plt, []) -> - Plt. - -table_insert_list(Plt, [{Key, Val}|Left]) -> - table_insert_list(table_insert(Plt, Key, Val), Left); -table_insert_list(Plt, []) -> - Plt. - -table_insert(Plt, Key, {_File, #contract{}, _Xtra} = C) -> - dict:store(Key, C, Plt). - -table_lookup(Plt, Obj) -> - case dict:find(Obj, Plt) of - error -> none; - {ok, Val} -> {value, Val} - end. + ets:tab2list(Plt). + +table_delete_module(Tab, Mod) -> + MS = ets:fun2ms(fun({{M, _F, _A}, _Val}) -> M =:= Mod; + ({_, _}) -> false + end), + _NumDeleted = ets:select_delete(Tab, MS), + Tab. + +table_delete_module1(Tab, Mod) -> + MS = ets:fun2ms(fun({{M, _F, _A}}) -> M =:= Mod end), + _NumDeleted = ets:select_delete(Tab, MS), + Tab. + +table_delete_module2(Tab, Mod) -> + true = ets:delete(Tab, Mod), + Tab. + +ets_table_delete_list(Tab, [H|T]) -> + ets:delete(Tab, H), + ets_table_delete_list(Tab, T); +ets_table_delete_list(Tab, []) -> + Tab. ets_table_lookup(Plt, Obj) -> try ets:lookup_element(Plt, Obj, 2) of @@ -633,25 +691,28 @@ ets_table_lookup(Plt, Obj) -> _:_ -> none end. -table_lookup_module(Plt, Mod) -> - List = dict:fold(fun(Key, Val, Acc) -> - case Key of - {Mod, _F, _A} -> [{Key, element(1, Val), - element(2, Val)}|Acc]; - _ -> Acc - end - end, [], Plt), +table_lookup_module(Tab, Mod) -> + MS = ets:fun2ms(fun({{M, F, A}, V}) when M =:= Mod -> + {{M, F, A}, V} end), + List = [begin + {V1, V2} = V, + {MFA, V1, V2} + end || {MFA, V} <- ets:select(Tab, MS)], case List =:= [] of true -> none; false -> {value, List} end. -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_all_modules(Tab) -> + Ks = ets:match(Tab, {'$1', '_'}, 100), + all_mods(Ks, sets:new()). + +all_mods('$end_of_table', S) -> + S; +all_mods({ListsOfKeys, Cont}, S) -> + S1 = lists:foldl(fun([{M, _F, _A}], S0) -> sets:add_element(M, S0) + end, S, ListsOfKeys), + all_mods(ets:match(Cont), S1). table_merge([H|T]) -> table_merge(T, H). @@ -659,7 +720,7 @@ table_merge([H|T]) -> table_merge([], Acc) -> Acc; table_merge([Plt|Plts], Acc) -> - NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc), + NewAcc = merge_tables(Plt, Acc), table_merge(Plts, NewAcc). table_disj_merge([H|T]) -> @@ -670,24 +731,18 @@ table_disj_merge([], 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), + NewAcc = merge_tables(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). sets_merge([], Acc) -> Acc; sets_merge([Plt|Plts], Acc) -> - NewAcc = sets:union(Plt, Acc), + NewAcc = merge_tables(Plt, Acc), sets_merge(Plts, NewAcc). sets_disj_merge([H|T]) -> @@ -696,13 +751,44 @@ sets_disj_merge([H|T]) -> sets_disj_merge([], Acc) -> Acc; sets_disj_merge([Plt|Plts], Acc) -> - case sets:is_disjoint(Plt, Acc) of + case table_is_disjoint(Plt, Acc) of true -> - NewAcc = sets:union(Plt, Acc), + NewAcc = merge_tables(Plt, Acc), sets_disj_merge(Plts, NewAcc); false -> throw({dialyzer_error, not_disjoint_plts}) end. +table_is_disjoint(T1, T2) -> + tab_is_disj(ets:first(T1), T1, T2). + +tab_is_disj('$end_of_table', _T1, _T2) -> + true; +tab_is_disj(K1, T1, T2) -> + case ets:member(T2, K1) of + false -> + tab_is_disj(ets:next(T1, K1), T1, T2); + true -> + false + end. + +merge_tables(T1, T2) -> + tab_merge(ets:first(T1), T1, T2). + +tab_merge('$end_of_table', T1, T2) -> + case ets:first(T1) of % no safe_fixtable()... + '$end_of_table' -> + true = ets:delete(T1), + T2; + Key -> + tab_merge(Key, T1, T2) + end; +tab_merge(K1, T1, T2) -> + Vs = ets:lookup(T1, K1), + NextK1 = ets:next(T1, K1), + true = ets:delete(T1, K1), + true = ets:insert(T2, Vs), + tab_merge(NextK1, T1, T2). + %%--------------------------------------------------------------------------- %% Debug utilities. @@ -720,7 +806,7 @@ pp_non_returning() -> io:format("= Loops =\n"), io:format("=========================================\n\n"), lists:foreach(fun({{M, F, _}, Type}) -> - io:format("~w:~w~s.\n", + io:format("~w:~tw~ts.\n", [M, F, dialyzer_utils:format_sig(Type)]) end, lists:sort(Unit)), io:format("\n"), @@ -730,7 +816,8 @@ pp_non_returning() -> lists:foreach(fun({{M, F, _}, Type}) -> io:format("~w:~w~s.\n", [M, F, dialyzer_utils:format_sig(Type)]) - end, lists:sort(None)). + end, lists:sort(None)), + delete(Plt). -spec pp_mod(atom()) -> 'ok'. @@ -742,8 +829,9 @@ pp_mod(Mod) when is_atom(Mod) -> lists:foreach(fun({{_, F, _}, Ret, Args}) -> T = erl_types:t_fun(Args, Ret), S = dialyzer_utils:format_sig(T), - io:format("-spec ~w~s.\n", [F, S]) + io:format("-spec ~tw~ts.\n", [F, S]) end, lists:sort(List)); none -> io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod]) - end. + end, + delete(Plt). diff --git a/lib/dialyzer/src/dialyzer_race_data_server.erl b/lib/dialyzer/src/dialyzer_race_data_server.erl index 3600491809..953cebfdf1 100644 --- a/lib/dialyzer/src/dialyzer_race_data_server.erl +++ b/lib/dialyzer/src/dialyzer_race_data_server.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_race_data_server.erl diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index bb43d1dcb8..7fe64c3e11 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%---------------------------------------------------------------------- %%% File : dialyzer_races.erl diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index 987da3aecf..48115bb683 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_succ_typings.erl @@ -36,7 +29,7 @@ -export([ find_succ_types_for_scc/2, refine_one_module/2, - find_required_by/2, + %% find_required_by/2, find_depends_on/2, collect_warnings/2, lookup_names/2 @@ -96,7 +89,7 @@ analyze_callgraph(Callgraph, Plt, Codeserver, TimingServer, Solvers, Parent) -> NewState = init_state_and_get_success_typings(Callgraph, Plt, Codeserver, TimingServer, Solvers, Parent), - dialyzer_plt:restore_full_plt(NewState#st.plt, Plt). + NewState#st.plt. %%-------------------------------------------------------------------- @@ -104,7 +97,7 @@ init_state_and_get_success_typings(Callgraph, Plt, Codeserver, TimingServer, Solvers, Parent) -> {SCCs, Callgraph1} = ?timing(TimingServer, "order", dialyzer_callgraph:finalize(Callgraph)), - State = #st{callgraph = Callgraph1, plt = dialyzer_plt:get_mini_plt(Plt), + State = #st{callgraph = Callgraph1, plt = Plt, codeserver = Codeserver, parent = Parent, timing_server = TimingServer, solvers = Solvers}, get_refined_success_typings(SCCs, State). @@ -145,18 +138,15 @@ get_warnings(Callgraph, Plt, DocPlt, Codeserver, init_state_and_get_success_typings(Callgraph, Plt, Codeserver, TimingServer, Solvers, Parent), Mods = dialyzer_callgraph:modules(InitState#st.callgraph), - MiniPlt = InitState#st.plt, - FindOpaques = lookup_and_find_opaques_fun(Codeserver), + Plt = InitState#st.plt, CWarns = - dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, - MiniPlt, FindOpaques), - MiniDocPlt = dialyzer_plt:get_mini_plt(DocPlt), + dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, Plt), ModWarns = ?timing(TimingServer, "warning", - get_warnings_from_modules(Mods, InitState, MiniDocPlt)), + get_warnings_from_modules(Mods, InitState, DocPlt)), {postprocess_warnings(CWarns ++ ModWarns, Codeserver), - dialyzer_plt:restore_full_plt(MiniPlt, Plt), - dialyzer_plt:restore_full_plt(MiniDocPlt, DocPlt)}. + Plt, + DocPlt}. get_warnings_from_modules(Mods, State, DocPlt) -> #st{callgraph = Callgraph, codeserver = Codeserver, @@ -168,16 +158,16 @@ get_warnings_from_modules(Mods, State, DocPlt) -> collect_warnings(M, {Codeserver, Callgraph, Plt, DocPlt}) -> ModCode = dialyzer_codeserver:lookup_mod_code(M, Codeserver), - Records = dialyzer_codeserver:lookup_mod_records(M, Codeserver), Contracts = dialyzer_codeserver:lookup_mod_contracts(M, Codeserver), AllFuns = collect_fun_info([ModCode]), %% Check if there are contracts for functions that do not exist Warnings1 = dialyzer_contracts:contracts_without_fun(Contracts, AllFuns, Callgraph), + Attrs = cerl:module_attrs(ModCode), + Records = dialyzer_codeserver:lookup_mod_records(M, Codeserver), {Warnings2, FunTypes} = dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Codeserver, Records), - Attrs = cerl:module_attrs(ModCode), Warnings3 = dialyzer_behaviours:check_callbacks(M, Attrs, Records, Plt, Codeserver), DocPlt = insert_into_doc_plt(FunTypes, Callgraph, DocPlt), @@ -242,10 +232,10 @@ refine_succ_typings(Modules, #st{codeserver = Codeserver, find_depends_on(SCC, {_Codeserver, Callgraph, _Plt, _Solvers}) -> dialyzer_callgraph:get_depends_on(SCC, Callgraph). --spec find_required_by(scc() | module(), fixpoint_init_data()) -> [scc()]. +%% -spec find_required_by(scc() | module(), fixpoint_init_data()) -> [scc()]. -find_required_by(SCC, {_Codeserver, Callgraph, _Plt, _Solvers}) -> - dialyzer_callgraph:get_required_by(SCC, Callgraph). +%% find_required_by(SCC, {_Codeserver, Callgraph, _Plt, _Solvers}) -> +%% dialyzer_callgraph:get_required_by(SCC, Callgraph). -spec lookup_names([label()], fixpoint_init_data()) -> [mfa_or_funlbl()]. @@ -257,24 +247,22 @@ lookup_names(Labels, {_Codeserver, Callgraph, _Plt, _Solvers}) -> refine_one_module(M, {CodeServer, Callgraph, Plt, _Solvers}) -> ModCode = dialyzer_codeserver:lookup_mod_code(M, CodeServer), AllFuns = collect_fun_info([ModCode]), - Records = dialyzer_codeserver:lookup_mod_records(M, CodeServer), FunTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt), + Records = dialyzer_codeserver:lookup_mod_records(M, CodeServer), NewFunTypes = dialyzer_dataflow:get_fun_types(ModCode, Plt, Callgraph, CodeServer, Records), - Contracts1 = dialyzer_codeserver:lookup_mod_contracts(M, CodeServer), - Contracts = orddict:from_list(dict:to_list(Contracts1)), - FindOpaques = find_opaques_fun(Records), - DecoratedFunTypes = - decorate_succ_typings(Contracts, Callgraph, NewFunTypes, FindOpaques), - %% ?debug("NewFunTypes ~p\n ~n", [dict:to_list(NewFunTypes)]), - %% ?debug("refine DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]), + {FunMFAContracts, ModOpaques} = + prepare_decoration(NewFunTypes, Callgraph, CodeServer), + DecoratedFunTypes = decorate_succ_typings(FunMFAContracts, ModOpaques), + %% ?Debug("NewFunTypes ~tp\n ~n", [NewFunTypes]), + %% ?debug("refine DecoratedFunTypes ~tp\n ~n", [DecoratedFunTypes]), debug_pp_functions("Refine", NewFunTypes, DecoratedFunTypes, Callgraph), case reached_fixpoint(FunTypes, DecoratedFunTypes) of true -> []; {false, NotFixpoint} -> ?debug("Not fixpoint\n", []), - Plt = insert_into_plt(dict:from_list(NotFixpoint), Callgraph, Plt), + Plt = insert_into_plt(orddict:from_list(NotFixpoint), Callgraph, Plt), [FunLbl || {FunLbl,_Type} <- NotFixpoint] end. @@ -288,22 +276,20 @@ reached_fixpoint_strict(OldTypes, NewTypes) -> end. reached_fixpoint(OldTypes0, NewTypes0, Strict) -> - MapFun = fun(_Key, Type) -> + MapFun = fun({Key, Type}) -> case is_failed_or_not_called_fun(Type) of - true -> failed_fun; - false -> erl_types:t_limit(Type, ?TYPE_LIMIT) + true -> {Key, failed_fun}; + false -> {Key, erl_types:t_limit(Type, ?TYPE_LIMIT)} end end, - OldTypes = dict:map(MapFun, OldTypes0), - NewTypes = dict:map(MapFun, NewTypes0), + OldTypes = lists:map(MapFun, orddict:to_list(OldTypes0)), + NewTypes = lists:map(MapFun, orddict:to_list(NewTypes0)), compare_types(OldTypes, NewTypes, Strict). is_failed_or_not_called_fun(Type) -> erl_types:any_none([erl_types:t_fun_range(Type)|erl_types:t_fun_args(Type)]). -compare_types(Dict1, Dict2, Strict) -> - List1 = lists:keysort(1, dict:to_list(Dict1)), - List2 = lists:keysort(1, dict:to_list(Dict2)), +compare_types(List1, List2, Strict) -> compare_types_1(List1, List2, Strict, []). compare_types_1([{X, _Type1}|Left1], [{X, failed_fun}|Left2], @@ -320,7 +306,7 @@ compare_types_1([{X, Type1}|Left1], [{X, Type2}|Left2], Strict, NotFixpoint) -> case Res of true -> compare_types_1(Left1, Left2, Strict, NotFixpoint); false -> - ?debug("Failed fixpoint for ~w: ~s =/= ~s\n", + ?debug("Failed fixpoint for ~w: ~ts =/= ~ts\n", [X, erl_types:t_to_string(Type1), erl_types:t_to_string(Type2)]), compare_types_1(Left1, Left2, Strict, [{X, Type2}|NotFixpoint]) end; @@ -348,88 +334,95 @@ find_succ_typings(SCCs, #st{codeserver = Codeserver, callgraph = Callgraph, -spec find_succ_types_for_scc(scc(), typesig_init_data()) -> [mfa_or_funlbl()]. -find_succ_types_for_scc(SCC, {Codeserver, Callgraph, Plt, Solvers}) -> - SCC_Info = [{MFA, - dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), - dialyzer_codeserver:lookup_mod_records(M, Codeserver)} - || {M, _, _} = MFA <- SCC], - Contracts1 = [{MFA, dialyzer_codeserver:lookup_mfa_contract(MFA, Codeserver)} - || {_, _, _} = MFA <- SCC], - Contracts2 = [{MFA, Contract} || {MFA, {ok, Contract}} <- Contracts1], - Contracts3 = orddict:from_list(Contracts2), +find_succ_types_for_scc(SCC0, {Codeserver, Callgraph, Plt, Solvers}) -> + SCC = [MFA || {_, _, _} = MFA <- SCC0], Label = dialyzer_codeserver:get_next_core_label(Codeserver), - AllFuns = collect_fun_info([Fun || {_MFA, {_Var, Fun}, _Rec} <- SCC_Info]), + AllFuns = lists:append( + [begin + {_Var, Fun} = + dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), + collect_fun_info([Fun]) + end || MFA <- SCC]), PropTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt), %% Assume that the PLT contains the current propagated types - FunTypes = dialyzer_typesig:analyze_scc(SCC_Info, Label, Callgraph, - Plt, PropTypes, Solvers), + FunTypes = dialyzer_typesig:analyze_scc(SCC, Label, Callgraph, + Codeserver, Plt, PropTypes, + Solvers), AllFunSet = sets:from_list([X || {X, _} <- AllFuns]), FilteredFunTypes = - dict:filter(fun(X, _) -> sets:is_element(X, AllFunSet) end, FunTypes), - FindOpaques = lookup_and_find_opaques_fun(Codeserver), - DecoratedFunTypes = - decorate_succ_typings(Contracts3, Callgraph, FilteredFunTypes, FindOpaques), + orddict:filter(fun(F, _T) -> sets:is_element(F, AllFunSet) + end, FunTypes), + {FunMFAContracts, ModOpaques} = + prepare_decoration(FilteredFunTypes, Callgraph, Codeserver), + DecoratedFunTypes = decorate_succ_typings(FunMFAContracts, ModOpaques), %% Check contracts + Contracts = orddict:from_list([{MFA, Contract} || + {_, {MFA, Contract}} <- FunMFAContracts]), PltContracts = - dialyzer_contracts:check_contracts(Contracts3, Callgraph, - DecoratedFunTypes, FindOpaques), - %% ?debug("FilteredFunTypes ~p\n ~n", [dict:to_list(FilteredFunTypes)]), - %% ?debug("SCC DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]), + dialyzer_contracts:check_contracts(Contracts, Callgraph, + DecoratedFunTypes, + ModOpaques), + %% ?debug("FilteredFunTypes ~tp\n ~n", [FilteredFunTypes]), + %% ?debug("SCC DecoratedFunTypes ~tp\n ~n", [DecoratedFunTypes]), debug_pp_functions("SCC", FilteredFunTypes, DecoratedFunTypes, Callgraph), - ContractFixpoint = - lists:all(fun({MFA, _C}) -> - %% Check the non-deleted PLT - case dialyzer_plt:lookup_contract(Plt, MFA) of - none -> false; - {value, _} -> true - end - end, PltContracts), + NewPltContracts = [MC || + {MFA, _C}=MC <- PltContracts, + %% Check the non-deleted PLT + not dialyzer_plt:is_contract(Plt, MFA)], + ContractFixpoint = NewPltContracts =:= [], Plt = insert_into_plt(DecoratedFunTypes, Callgraph, Plt), - Plt = dialyzer_plt:insert_contract_list(Plt, PltContracts), + Plt = dialyzer_plt:insert_contract_list(Plt, NewPltContracts), case (ContractFixpoint andalso reached_fixpoint_strict(PropTypes, DecoratedFunTypes)) of true -> []; false -> - ?debug("Not fixpoint for: ~w\n", [AllFuns]), + ?debug("Not fixpoint for: ~tw\n", [AllFuns]), [Fun || {Fun, _Arity} <- AllFuns] end. -decorate_succ_typings(Contracts, Callgraph, FunTypes, FindOpaques) -> - F = fun(Label, Type) -> +prepare_decoration(FunTypes, Callgraph, Codeserver) -> + F = fun({Label, _Type}=LabelType, Acc) -> case dialyzer_callgraph:lookup_name(Label, Callgraph) of {ok, MFA} -> - case orddict:find(MFA, Contracts) of + case dialyzer_codeserver:lookup_mfa_contract(MFA, Codeserver) of {ok, {_FileLine, Contract, _Xtra}} -> - Args = dialyzer_contracts:get_contract_args(Contract), - Ret = dialyzer_contracts:get_contract_return(Contract), - C = erl_types:t_fun(Args, Ret), - {M, _, _} = MFA, - Opaques = FindOpaques(M), - erl_types:t_decorate_with_opaque(Type, C, Opaques); - error -> Type + [{LabelType, {MFA, Contract}}|Acc]; + error -> [{LabelType, no}|Acc] end; - error -> Type + error -> [{LabelType, no}|Acc] end end, - dict:map(F, FunTypes). - -lookup_and_find_opaques_fun(Codeserver) -> - fun(Module) -> - Records = dialyzer_codeserver:lookup_mod_records(Module, Codeserver), - (find_opaques_fun(Records))(Module) - end. + Contracts = lists:foldl(F, [], orddict:to_list(FunTypes)), + ModOpaques = + [{M, lookup_opaques(M, Codeserver)} || + M <- lists:usort([M || {_LabelType, {{M, _, _}, _Con}} <- Contracts])], + {Contracts, orddict:from_list(ModOpaques)}. + +decorate_succ_typings(FunTypesContracts, ModOpaques) -> + F = fun({{Label, Type}, {{M, _, _}, Contract}}) -> + Args = dialyzer_contracts:get_contract_args(Contract), + Ret = dialyzer_contracts:get_contract_return(Contract), + C = erl_types:t_fun(Args, Ret), + {M, Opaques} = lists:keyfind(M, 1, ModOpaques), + R = erl_types:t_decorate_with_opaque(Type, C, Opaques), + {Label, R}; + ({LabelType, no}) -> + LabelType + end, + orddict:from_list(lists:map(F, FunTypesContracts)). -find_opaques_fun(Records) -> - fun(_Module) -> erl_types:t_opaque_from_records(Records) end. +lookup_opaques(Module, Codeserver) -> + Records = dialyzer_codeserver:lookup_mod_records(Module, Codeserver), + erl_types:t_opaque_from_records(Records). get_fun_types_from_plt(FunList, Callgraph, Plt) -> - get_fun_types_from_plt(FunList, Callgraph, Plt, dict:new()). + get_fun_types_from_plt(FunList, Callgraph, Plt, []). 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(Left, Callgraph, Plt, [{FunLabel, Type}|Map]); get_fun_types_from_plt([], _Callgraph, _Plt, Map) -> - Map. + orddict:from_list(Map). collect_fun_info(Trees) -> collect_fun_info(Trees, []). @@ -465,7 +458,7 @@ insert_into_plt(SuccTypes0, Callgraph, Plt) -> dialyzer_plt:insert_list(Plt, SuccTypes). format_succ_types(SuccTypes, Callgraph) -> - format_succ_types(dict:to_list(SuccTypes), Callgraph, []). + format_succ_types(SuccTypes, Callgraph, []). format_succ_types([{Label, Type0}|Left], Callgraph, Acc) -> Type = erl_types:t_limit(Type0, ?TYPE_LIMIT+1), @@ -478,28 +471,26 @@ format_succ_types([], _Callgraph, Acc) -> -ifdef(DEBUG). debug_pp_succ_typings(SuccTypes) -> ?debug("Succ typings:\n", []), - [?debug(" ~w :: ~s\n", + [?debug(" ~tw :: ~ts\n", [MFA, erl_types:t_to_string(erl_types:t_fun(ArgT, RetT))]) || {MFA, {RetT, ArgT}} <- SuccTypes], ?debug("Contracts:\n", []), - [?debug(" ~w :: ~s\n", + [?debug(" ~tw :: ~ts\n", [MFA, erl_types:t_to_string(erl_types:t_fun(ArgT, RetFun(ArgT)))]) || {MFA, {contract, RetFun, ArgT}} <- SuccTypes], ?debug("\n", []), ok. -debug_pp_functions(Header, FunTypes, DecoratedFunTypes, Callgraph) -> +debug_pp_functions(Header, FTypes, DTypes, Callgraph) -> ?debug("FunTypes (~s)\n", [Header]), - FTypes = lists:keysort(1, dict:to_list(FunTypes)), - DTypes = lists:keysort(1, dict:to_list(DecoratedFunTypes)), Fun = fun({{Label, Type},{Label, DecoratedType}}) -> Name = lookup_name(Label, Callgraph), - ?debug("~w (~w): ~s\n", + ?debug("~tw (~w): ~ts\n", [Name, Label, erl_types:t_to_string(Type)]), case erl_types:t_is_equal(Type, DecoratedType) of true -> ok; false -> - ?debug(" With opaque types: ~s\n", + ?debug(" With opaque types: ~ts\n", [erl_types:t_to_string(DecoratedType)]) end end, diff --git a/lib/dialyzer/src/dialyzer_timing.erl b/lib/dialyzer/src/dialyzer_timing.erl index aa71318d8e..056ccd51d8 100644 --- a/lib/dialyzer/src/dialyzer_timing.erl +++ b/lib/dialyzer/src/dialyzer_timing.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_timing.erl diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 1787b66192..d03326ec97 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_typesig.erl @@ -29,7 +22,7 @@ -module(dialyzer_typesig). --export([analyze_scc/6]). +-export([analyze_scc/7]). -export([get_safe_underapprox/2]). %%-import(helper, %% 'helper' could be any module doing sanity checks... @@ -48,7 +41,7 @@ t_is_float/1, t_is_fun/1, t_is_integer/1, t_non_neg_integer/0, t_is_list/1, t_is_nil/1, t_is_none/1, t_is_number/1, - t_is_singleton/1, + t_is_singleton/1, t_is_none_or_unit/1, t_limit/2, t_list/0, t_list/1, t_list_elements/1, t_nonempty_list/1, t_maybe_improper_list/0, @@ -88,7 +81,7 @@ -record(constraint_list, {type :: 'conj' | 'disj', list :: [constr()], deps :: deps(), - masks = maps:new() :: #{dep() => mask()}, + masks :: #{dep() => mask()} | 'undefined', id :: {'list', dep()} | 'undefined'}). -type constraint_list() :: #constraint_list{}. @@ -101,13 +94,14 @@ -type types() :: erl_types:type_table(). --type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, types()}]. -type typesig_funmap() :: #{type_var() => type_var()}. --type prop_types() :: dict:dict(label(), types()). +-type prop_types() :: orddict:orddict(label(), erl_types:erl_type()). +-type dict_prop_types() :: dict:dict(label(), erl_types:erl_type()). -record(state, {callgraph :: dialyzer_callgraph:callgraph() | 'undefined', + cserver :: dialyzer_codeserver:codeserver(), cs = [] :: [constr()], cmap = maps:new() :: #{type_var() => constr()}, fun_map = maps:new() :: typesig_funmap(), @@ -120,8 +114,8 @@ self_rec :: 'false' | erl_types:erl_type(), plt :: dialyzer_plt:plt() | 'undefined', - prop_types = dict:new() :: prop_types(), - records = dict:new() :: types(), + prop_types = dict:new() :: dict_prop_types(), + mod_records = [] :: [{module(), types()}], scc = [] :: ordsets:ordset(type_var()), mfas :: [mfa()], solvers = [] :: [solver()] @@ -146,9 +140,11 @@ -ifdef(DEBUG). -define(debug(__String, __Args), io:format(__String, __Args)). -define(mk_fun_var(Fun, Vars), mk_fun_var(?LINE, Fun, Vars)). +-define(pp_map(S, M), pp_map(S, M)). -else. -define(debug(__String, __Args), ok). -define(mk_fun_var(Fun, Vars), mk_fun_var(Fun, Vars)). +-define(pp_map(S, M), ok). -endif. %% ============================================================================ @@ -160,11 +156,10 @@ %%----------------------------------------------------------------------------- %% Analysis of strongly connected components. %% -%% analyze_scc(SCC, NextLabel, CallGraph, PLT, PropTypes, Solvers) -> FunTypes +%% analyze_scc(SCC, NextLabel, CallGraph, CodeServer, +%% PLT, PropTypes, Solvers) -> FunTypes %% -%% SCC - [{MFA, Def, Records}] -%% where Def = {Var, Fun} as in the Core Erlang module definitions. -%% Records = dict(RecName, {Arity, [{FieldName, FieldType}]}) +%% SCC - [{MFA}] %% NextLabel - An integer that is higher than any label in the code. %% CallGraph - A callgraph as produced by dialyzer_callgraph.erl %% Note: The callgraph must have been built with all the @@ -176,27 +171,23 @@ %% Solvers - User specified solvers. %%----------------------------------------------------------------------------- --spec analyze_scc(typesig_scc(), label(), +-spec analyze_scc([mfa()], label(), dialyzer_callgraph:callgraph(), + dialyzer_codeserver:codeserver(), dialyzer_plt:plt(), prop_types(), [solver()]) -> prop_types(). -analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes, Solvers0) -> +analyze_scc(SCC, NextLabel, CallGraph, CServer, Plt, PropTypes, Solvers0) -> Solvers = solvers(Solvers0), - assert_format_of_scc(SCC), - State1 = new_state(SCC, NextLabel, CallGraph, Plt, PropTypes, Solvers), - DefSet = add_def_list([Var || {_MFA, {Var, _Fun}, _Rec} <- SCC], sets:new()), - State2 = traverse_scc(SCC, DefSet, State1), + State1 = new_state(SCC, NextLabel, CallGraph, CServer, Plt, PropTypes, + Solvers), + DefSet = add_def_list(maps:values(State1#state.name_map), sets:new()), + State2 = traverse_scc(SCC, CServer, DefSet, State1), State3 = state__finalize(State2), Funs = state__scc(State3), pp_constrs_scc(Funs, State3), constraints_to_dot_scc(Funs, State3), T = solve(Funs, State3), - dict:from_list(maps:to_list(T)). - -assert_format_of_scc([{_MFA, {_Var, _Fun}, _Records}|Left]) -> - assert_format_of_scc(Left); -assert_format_of_scc([]) -> - ok. + orddict:from_list(maps:to_list(T)). solvers([]) -> [v2]; solvers(Solvers) -> Solvers. @@ -207,12 +198,14 @@ solvers(Solvers) -> Solvers. %% %% ============================================================================ -traverse_scc([{_MFA, Def, Rec}|Left], DefSet, AccState) -> - TmpState1 = state__set_rec_dict(AccState, Rec), +traverse_scc([{M,_,_}=MFA|Left], Codeserver, DefSet, AccState) -> + TmpState1 = state__set_module(AccState, M), + Def = dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), DummyLetrec = cerl:c_letrec([Def], cerl:c_atom(foo)), - {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState1), - traverse_scc(Left, DefSet, NewAccState); -traverse_scc([], _DefSet, AccState) -> + TmpState2 = state__new_constraint_context(TmpState1), + {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState2), + traverse_scc(Left, Codeserver, DefSet, NewAccState); +traverse_scc([], _Codeserver, _DefSet, AccState) -> AccState. traverse(Tree, DefinedVars, State) -> @@ -478,12 +471,11 @@ traverse(Tree, DefinedVars, State) -> true -> %% Check if a record is constructed. Arity = length(Fields), - 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), - {State3, TupleType} + case lookup_record(State2, cerl:atom_val(Tag), Arity) of + {error, State3} -> {State3, TupleType}; + {ok, RecType, State3} -> + State4 = state__store_conj(TupleType, sub, RecType, State3), + {State4, TupleType} end; false -> {State2, TupleType} end; @@ -536,13 +528,14 @@ traverse(Tree, DefinedVars, State) -> false -> t_any(); true -> MT = t_inf(lookup_type(MapVar, Map), t_map()), - case t_is_none(MT) of + case t_is_none_or_unit(MT) of true -> t_none(); false -> DisjointFromKeyType = fun(ShadowKey) -> - t_is_none(t_inf(lookup_type(ShadowKey, Map), - KeyType)) + ST = t_inf(lookup_type(ShadowKey, Map), + KeyType), + t_is_none_or_unit(ST) end, case lists:all(DisjointFromKeyType, ShadowKeys) of true -> t_map_get(KeyType, MT); @@ -575,7 +568,8 @@ traverse(Tree, DefinedVars, State) -> case cerl:is_literal(OpTree) andalso cerl:concrete(OpTree) =:= exact of true -> - case t_is_none(t_inf(ShadowedKeys, KeyType)) of + ST = t_inf(ShadowedKeys, KeyType), + case t_is_none_or_unit(ST) of true -> t_map_put({KeyType, t_any()}, AccType); false -> @@ -779,7 +773,7 @@ handle_call(Call, DefinedVars, State) -> {ok, Var} -> %% This is part of the SCC currently analyzed. %% Intercept and change this to an apply instead. - ?debug("Found the call to ~w\n", [MFA]), + ?debug("Found the call to ~tw\n", [MFA]), Label = cerl_trees:get_label(Call), Apply = cerl:ann_c_apply([{label, Label}], Var, Args), traverse(Apply, DefinedVars, State) @@ -1448,7 +1442,6 @@ 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, ArgFun = fun(Map) -> case t_is_any_atom(true, lookup_type(Dst, Map)) of @@ -1465,10 +1458,10 @@ 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 lookup_record(Records, TagVal, ArityVal - 1) of - {ok, Type} -> + case lookup_record(State, TagVal, ArityVal - 1) of + {ok, Type, _NewState} -> Type; - error -> GenRecord + {error, _NewState} -> GenRecord end; _ -> GenRecord end; @@ -1797,11 +1790,11 @@ get_bif_test_constr(Dst, Arg, Type, _State) -> %%============================================================================= solve([Fun], State) -> - ?debug("============ Analyzing Fun: ~w ===========\n", + ?debug("============ Analyzing Fun: ~tw ===========\n", [debug_lookup_name(Fun)]), solve_fun(Fun, map_new(), State); solve([_|_] = SCC, State) -> - ?debug("============ Analyzing SCC: ~w ===========\n", + ?debug("============ Analyzing SCC: ~tw ===========\n", [[debug_lookup_name(F) || F <- SCC]]), Users = comp_users(SCC, State), solve_scc(SCC, map_new(), State, Users, _ToSolve=SCC, false). @@ -1837,13 +1830,13 @@ solve_scc(SCC, Map, State, Users, ToSolve, TryingUnit) -> erase_type(t_var_name(Fun), AccFunMap) end, Map, ToSolve), Map1 = enter_type_lists(Vars, RecTypes, CleanMap), - ?debug("Checking SCC: ~w\n", [[debug_lookup_name(F) || F <- SCC]]), + ?debug("Checking SCC: ~tw\n", [[debug_lookup_name(F) || F <- SCC]]), SolveFun = fun(X, Y) -> scc_fold_fun(X, Y, State) end, Map2 = lists:foldl(SolveFun, Map1, ToSolve), Updated = updated_vars_only(Vars, Map, Map2), case Updated =:= [] of true -> - ?debug("SCC ~w reached fixpoint\n", [SCC]), + ?debug("SCC ~tw reached fixpoint\n", [SCC]), NewTypes = unsafe_lookup_type_list(Funs, Map2), case erl_types:any_none([t_fun_range(T) || T <- NewTypes]) andalso TryingUnit =:= false of @@ -1859,7 +1852,7 @@ solve_scc(SCC, Map, State, Users, ToSolve, TryingUnit) -> Map2 end; false -> - ?debug("SCC ~w did not reach fixpoint\n", [SCC]), + ?debug("SCC ~tw did not reach fixpoint\n", [SCC]), ToSolve1 = affected(Updated, Users), solve_scc(SCC, Map2, State, Users, ToSolve1, TryingUnit) end. @@ -1883,8 +1876,8 @@ scc_fold_fun(F, FunMap, State) -> error -> enter_type(F, NewType, FunMap) end, - ?debug("Done solving for function ~w :: ~s\n", [debug_lookup_name(F), - format_type(NewType)]), + ?debug("Done solving for function ~tw :: ~ts\n", [debug_lookup_name(F), + format_type(NewType)]), NewFunMap. solve(Fun, Cs, FunMap, State) -> @@ -1903,7 +1896,7 @@ solver(Solver, SolveFun) -> [Solver, _R, 60]), throw(error) catch E:R -> - io:format("Solver ~w failed: ~w:~p\n ~p\n", + io:format("Solver ~w failed: ~w:~p\n ~tp\n", [Solver, E, R, erlang:get_stacktrace()]), throw(error) end. @@ -1925,8 +1918,8 @@ check_solutions([{S1,Map1,_Time1}|Maps], Fun, S, Map) -> check_solutions(Maps, Fun, S1, Map1); false -> ?debug("Constraint solvers do not agree on ~w\n", [Fun]), - pp_map(atom_to_list(S), Map), - pp_map(atom_to_list(S1), Map1), + ?pp_map(atom_to_list(S), Map), + ?pp_map(atom_to_list(S1), Map1), io:format("A bug was found. Please report it, and use the option " "`--solver v1' until the bug has been fixed.\n"), throw(error) @@ -1940,9 +1933,9 @@ sane_maps(Map1, Map2, Keys, _S1, _S2) -> true -> true; false -> ?debug("Constraint solvers do not agree on ~w\n", [Key]), - ?debug("~w: ~s\n", + ?debug("~w: ~ts\n", [_S1, format_type(unsafe_lookup_type(Key, Map1))]), - ?debug("~w: ~s\n", + ?debug("~w: ~ts\n", [_S2, format_type(unsafe_lookup_type(Key, Map2))]), false end @@ -1974,8 +1967,8 @@ v2_solve(#constraint_ref{id = Id}, Map, V2State) -> v2_solve_reference(Id, Map, V2State). v2_solve_reference(Id, Map, V2State0) -> - ?debug("Checking ref to fun: ~w\n", [debug_lookup_name(Id)]), - pp_map("Map", Map), + ?debug("Checking ref to fun: ~tw\n", [debug_lookup_name(Id)]), + ?pp_map("Map", Map), pp_constr_data("solve_ref", V2State0), Map1 = restore_local_map(V2State0, Id, Map), State = V2State0#v2_state.state, @@ -1988,7 +1981,7 @@ v2_solve_reference(Id, Map, V2State0) -> {FunType, V2State} = case Res of {error, V2State1} -> - ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), + ?debug("Error solving for function ~tp\n", [debug_lookup_name(Id)]), Arity = state__fun_arity(Id, State), FunType0 = case state__prop_domain(t_var_name(Id), State) of @@ -1997,12 +1990,12 @@ v2_solve_reference(Id, Map, V2State0) -> end, {FunType0, V2State1}; {ok, NewMap, V2State1, U} -> - ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), + ?debug("Done solving fun: ~tp\n", [debug_lookup_name(Id)]), FunType0 = lookup_type(Id, NewMap), V2State2 = save_local_map(V2State1, Id, U, NewMap), {FunType0, V2State2} end, - ?debug("ref Id=~w Assigned ~s\n", [Id, format_type(FunType)]), + ?debug("ref Id=~w Assigned ~ts\n", [Id, format_type(FunType)]), {NewMap1, U1} = enter_var_type(Id, FunType, Map), {NewMap2, U2} = case state__get_rec_var(Id, State) of @@ -2012,10 +2005,10 @@ v2_solve_reference(Id, Map, V2State0) -> {ok, NewMap2, V2State, lists:umerge(U1, U2)}. v2_solve_self_recursive(Cs, Map, Id, RecType0, V2State0) -> - ?debug("Solving self recursive ~w\n", [debug_lookup_name(Id)]), + ?debug("Solving self recursive ~tw\n", [debug_lookup_name(Id)]), State = V2State0#v2_state.state, {ok, RecVar} = state__get_rec_var(Id, State), - ?debug("OldRecType ~s\n", [format_type(RecType0)]), + ?debug("OldRecType ~ts\n", [format_type(RecType0)]), RecType = t_limit(RecType0, ?TYPE_LIMIT), {Map1, U0} = enter_var_type(RecVar, RecType, Map), V2State1 = save_updated_vars1(V2State0, Cs, U0), % Probably not necessary @@ -2031,7 +2024,7 @@ v2_solve_self_recursive(Cs, Map, Id, RecType0, V2State0) -> Error end; {ok, NewMap, V2State, U} -> - pp_map("recursive finished", NewMap), + ?pp_map("recursive finished", NewMap), NewRecType = unsafe_lookup_type(Id, NewMap), case is_equal(NewRecType, RecType0) of true -> @@ -2049,7 +2042,7 @@ enter_var_type(Var, Type, Map0) -> v2_solve_disjunct(Disj, Map, V2State0) -> #constraint_list{type = disj, id = _Id, list = Cs, masks = Masks} = Disj, ?debug("disjunct Id=~w~n", [_Id]), - pp_map("Map", Map), + ?pp_map("Map", Map), pp_constr_data("disjunct", V2State0), case get_flags(V2State0, Disj) of {V2State1, failed_list} -> {error, V2State1}; % cannot happen @@ -2077,7 +2070,7 @@ v2_solve_disjunct(Disj, Map, V2State0) -> U1 = [V || V <- U0, var_occurs_everywhere(V, Masks, NotFailed)], NewMap = join_maps(U1, MapL, Map), - pp_map("NewMap", NewMap), + ?pp_map("NewMap", NewMap), U = updated_vars_only(U1, Map, NewMap), ?debug("disjunct finished _Id=~w\n", [_Id]), {ok, NewMap, V2State, U} @@ -2100,12 +2093,18 @@ v2_solve_disj([I|Is], [C|Cs], I, Map0, V2State0, UL, MapL, Eval, Uneval, {ok, Map, V2State1, U} -> ?debug("disj I=~w U=~w~n", [I, U]), V2State = save_local_map(V2State1, Id, U, Map), - pp_map("DMap", Map), + ?pp_map("DMap", Map), v2_solve_disj(Is, Cs, I+1, Map0, V2State, [U|UL], [Map|MapL], [I|Eval], Uneval, Failed0) end; v2_solve_disj([], [], _I, _Map, V2State, UL, MapL, Eval, Uneval, Failed) -> {ok, V2State, lists:reverse(Eval), UL, MapL, lists:reverse(Uneval), Failed}; +v2_solve_disj([every_i], Cs, I, Map, V2State, UL, MapL, Eval, Uneval, Failed) -> + NewIs = case Cs of + [] -> []; + _ -> [I, every_i] + end, + v2_solve_disj(NewIs, Cs, I, Map, V2State, UL, MapL, Eval, Uneval, Failed); v2_solve_disj(Is, [C|Cs], I, Map, V2State, UL, MapL, Eval, Uneval0, Failed) -> Uneval = [{I,C#constraint_list.id} || not is_failed_list(C, V2State)] ++ Uneval0, @@ -2120,9 +2119,9 @@ save_local_map(#v2_state{constr_data = ConData}=V2State, Id, U, Map) -> end, ?debug("save local map Id=~w:\n", [Id]), Part = lists:ukeymerge(1, lists:keysort(1, Part0), Part1), - pp_map("New Part", maps:from_list(Part0)), - pp_map("Old Part", maps:from_list(Part1)), - pp_map(" => Part", maps:from_list(Part)), + ?pp_map("New Part", maps:from_list(Part0)), + ?pp_map("Old Part", maps:from_list(Part1)), + ?pp_map(" => Part", maps:from_list(Part)), V2State#v2_state{constr_data = maps:put(Id, {Part,[]}, ConData)}. restore_local_map(#v2_state{constr_data = ConData}, Id, Map0) -> @@ -2133,10 +2132,10 @@ restore_local_map(#v2_state{constr_data = ConData}, Id, Map0) -> {ok, {Part0,U}} -> Part = [KV || {K,_V} = KV <- Part0, not lists:member(K, U)], ?debug("restore local map Id=~w U=~w\n", [Id, U]), - pp_map("Part", maps:from_list(Part)), - pp_map("Map0", Map0), + ?pp_map("Part", maps:from_list(Part)), + ?pp_map("Map0", Map0), Map = lists:foldl(fun({K,V}, D) -> maps:put(K, V, D) end, Map0, Part), - pp_map("Map", Map), + ?pp_map("Map", Map), Map end. @@ -2177,7 +2176,7 @@ v2_solve_conj([I|Is], [Cs|Tail], I, Map0, Conj, IsFlat, V2State0, M = lists:keydelete(I, 1, vars_per_child(U, Masks)), {V2State2, NewF0} = save_updated_vars_list(AllCs, M, V2State1), {NewF, F} = lists:splitwith(fun(J) -> J < I end, NewF0), - Is1 = lists:umerge(Is, F), + Is1 = umerge_mask(Is, F), NewFs = [NewF|NewFs0], v2_solve_conj(Is1, Tail, I+1, Map, Conj, IsFlat, V2State2, [U|UL], NewFs, VarsUp, LastMap, LastFlags) @@ -2199,6 +2198,14 @@ v2_solve_conj([], _Cs, _I, Map, Conj, IsFlat, V2State, UL, NewFs, VarsUp, v2_solve_conj(NewFlags, Cs, 1, Map, Conj, IsFlat, V2State, [], [], [U|VarsUp], Map, NewFlags) end; +v2_solve_conj([every_i], Cs, I, Map, Conj, IsFlat, V2State, UL, NewFs, VarsUp, + LastMap, LastFlags) -> + NewIs = case Cs of + [] -> []; + _ -> [I, every_i] + end, + v2_solve_conj(NewIs, Cs, I, Map, Conj, IsFlat, V2State, UL, NewFs, VarsUp, + LastMap, LastFlags); v2_solve_conj(Is, [_|Tail], I, Map, Conj, IsFlat, V2State, UL, NewFs, VarsUp, LastMap, LastFlags) -> v2_solve_conj(Is, Tail, I+1, Map, Conj, IsFlat, V2State, UL, NewFs, VarsUp, @@ -2215,7 +2222,12 @@ report_detected_loop(_) -> add_mask_to_flags(Flags, [Im|M], I, L) when I > Im -> add_mask_to_flags(Flags, M, I, [Im|L]); add_mask_to_flags(Flags, [_|M], _I, L) -> - {lists:umerge(M, Flags), lists:reverse(L)}. + {umerge_mask(Flags, M), lists:reverse(L)}. + +umerge_mask([every_i]=Is, _F) -> + Is; +umerge_mask(Is, F) -> + lists:umerge(Is, F). get_mask(V, Masks) -> case maps:find(V, Masks) of @@ -2229,7 +2241,7 @@ get_flags(#v2_state{constr_data = ConData}=V2State0, C) -> error -> ?debug("get_flags Id=~w Flags=all ~w\n", [Id, length(Cs)]), V2State = V2State0#v2_state{constr_data = maps:put(Id, {[],[]}, ConData)}, - {V2State, lists:seq(1, length(Cs))}; + {V2State, [every_i]}; {ok, failed} -> {V2State0, failed_list}; {ok, {Part,U}} when U =/= [] -> @@ -2279,7 +2291,7 @@ pp_constr_data(_Tag, #v2_state{constr_data = D}) -> case _PartU of {_Part, _U} -> io:format("Id: ~w Vars: ~w\n", [_Id, _U]), - [pp_map("Part", maps:from_list(_Part)) || _Part =/= []]; + [?pp_map("Part", maps:from_list(_Part)) || _Part =/= []]; failed -> io:format("Id: ~w failed list\n", [_Id]) end @@ -2308,7 +2320,7 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, error -> {map_new(), false}; {ok, M} -> {M, true} end, - ?debug("Checking ref to fun: ~w\n", [debug_lookup_name(Id)]), + ?debug("Checking ref to fun: ~tw\n", [debug_lookup_name(Id)]), %% Note: mk_constraint_ref() has already removed Id from Deps. The %% reason for doing it there is that it makes it easy for %% calculate_masks() to make the corresponding adjustment for @@ -2330,7 +2342,7 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, {NewMapDict, FunType} = case Res of {error, NewMapDict0} -> - ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), + ?debug("Error solving for function ~tp\n", [debug_lookup_name(Id)]), Arity = state__fun_arity(Id, State), FunType0 = case state__prop_domain(t_var_name(Id), State) of @@ -2339,11 +2351,11 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, end, {NewMapDict0, FunType0}; {ok, NewMapDict0, NewMap} -> - ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), + ?debug("Done solving fun: ~tp\n", [debug_lookup_name(Id)]), FunType0 = lookup_type(Id, NewMap), {NewMapDict0, FunType0} end, - ?debug(" Id=~w Assigned ~s\n", [Id, format_type(FunType)]), + ?debug(" Id=~w Assigned ~ts\n", [Id, format_type(FunType)]), NewMap1 = enter_type(Id, FunType, Map), NewMap2 = case state__get_rec_var(Id, State) of @@ -2365,21 +2377,21 @@ solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id}, true -> case Check andalso maps_are_equal(OldLocalMap, Map, Deps) of true -> - ?debug("~w equal ~w\n", [Type, Id]), + ?debug("~tw equal ~w\n", [Type, Id]), {ok, MapDict, Map}; false -> - ?debug("~w not equal: ~w. Solving\n", [Type, Id]), + ?debug("~tw not equal: ~w. Solving\n", [Type, Id]), solve_clist(Cs, Type, Id, Deps, MapDict, Map, State) end end. solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) -> - ?debug("Solving self recursive ~w\n", [debug_lookup_name(Id)]), + ?debug("Solving self recursive ~tw\n", [debug_lookup_name(Id)]), {ok, RecVar} = state__get_rec_var(Id, State), - ?debug("OldRecType ~s\n", [format_type(RecType0)]), + ?debug("OldRecType ~ts\n", [format_type(RecType0)]), RecType = t_limit(RecType0, ?TYPE_LIMIT), Map1 = enter_type(RecVar, RecType, erase_type(t_var_name(Id), Map)), - pp_map("Map1", Map1), + ?pp_map("Map1", Map1), case solve_ref_or_list(Cs, Map1, MapDict, State) of {error, _} = Error -> case t_is_none(RecType0) of @@ -2392,7 +2404,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) -> Error end; {ok, NewMapDict, NewMap} -> - pp_map("NewMap", NewMap), + ?pp_map("NewMap", NewMap), NewRecType = unsafe_lookup_type(Id, NewMap), case is_equal(NewRecType, RecType0) of true -> @@ -2458,7 +2470,7 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) -> LhsType = lookup_type(Lhs, Map), RhsType = lookup_type(Rhs, Map), Inf = t_inf(LhsType, RhsType), - ?debug("Solving: ~s :: ~s ~w ~s :: ~s\n\tInf: ~s\n", + ?debug("Solving: ~ts :: ~ts ~w ~ts :: ~ts\n\tInf: ~ts\n", [format_type(Lhs), format_type(LhsType), Op, format_type(Rhs), format_type(RhsType), format_type(Inf)]), case t_is_none(Inf) of @@ -2490,14 +2502,14 @@ solve_subtype(Type, Inf, Map) -> {_, List} -> {ok, enter_type_list(List, Map)} catch throw:{mismatch, _T1, _T2} -> - ?debug("Mismatch between ~s and ~s\n", + ?debug("Mismatch between ~ts and ~ts\n", [format_type(_T1), format_type(_T2)]), error end. %% end. report_failed_constraint(_C, _Map) -> - ?debug("+++++++++++\nFailed: ~s :: ~s ~w ~s :: ~s\n+++++++++++\n", + ?debug("+++++++++++\nFailed: ~ts :: ~ts ~w ~ts :: ~ts\n+++++++++++\n", [format_type(_C#constraint.lhs), format_type(lookup_type(_C#constraint.lhs, _Map)), _C#constraint.op, @@ -2555,7 +2567,7 @@ maps_are_equal_1(Map1, Map2, [H|Tail]) -> case is_equal(T1, T2) of true -> maps_are_equal_1(Map1, Map2, Tail); false -> - ?debug("~w: ~s =/= ~s\n", [H, format_type(T1), format_type(T2)]), + ?debug("~w: ~ts =/= ~ts\n", [H, format_type(T1), format_type(T2)]), false end; maps_are_equal_1(_Map1, _Map2, []) -> @@ -2583,7 +2595,7 @@ prune_keys(Map1, Map2, Deps) -> end. enter_type(Key, Val, Map) when is_integer(Key) -> - ?debug("Entering ~s :: ~s\n", [format_type(t_var(Key)), format_type(Val)]), + ?debug("Entering ~ts :: ~ts\n", [format_type(t_var(Key)), format_type(Val)]), %% Keep any() in the map if it is opaque: case is_equal(Val, t_any()) of true -> @@ -2592,7 +2604,7 @@ enter_type(Key, Val, Map) when is_integer(Key) -> LimitedVal = t_limit(Val, ?INTERNAL_TYPE_LIMIT), case is_equal(LimitedVal, Val) of true -> ok; - false -> ?debug("LimitedVal ~s\n", [format_type(LimitedVal)]) + false -> ?debug("LimitedVal ~ts\n", [format_type(LimitedVal)]) end, case maps:find(Key, Map) of {ok, Value} -> @@ -2627,7 +2639,7 @@ enter_type2(Key, Val, Map) -> {Map1, [Key || not is_same(Key, Map, Map1)]}. map_store(Key, Val, Map) -> - ?debug("Storing ~w :: ~s\n", [Key, format_type(Val)]), + ?debug("Storing ~tw :: ~ts\n", [Key, format_type(Val)]), maps:put(Key, Val, Map). erase_type(Key, Map) -> @@ -2691,22 +2703,20 @@ is_same(Key, Map1, Map2) -> is_equal(Type1, Type2) -> t_is_equal(Type1, Type2). -pp_map(_S, _Map) -> - ?debug("\t~s: ~p\n", - [_S, [{X, lists:flatten(format_type(Y))} || - {X, Y} <- lists:keysort(1, maps:to_list(_Map))]]). - %% ============================================================================ %% %% The State. %% %% ============================================================================ -new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes, Solvers) -> - List = [{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0], +new_state(MFAs, NextLabel, CallGraph, CServer, Plt, PropTypes0, Solvers) -> + List_SCC = + [begin + {Var, Label} = dialyzer_codeserver:lookup_mfa_var_label(MFA, CServer), + {{MFA, Var}, t_var(Label)} + end || MFA <- MFAs], + {List, SCC} = lists:unzip(List_SCC), NameMap = maps:from_list(List), - MFAs = [MFA || {MFA, _Var} <- List], - SCC = [mk_var(Fun) || {_MFA, {_Var, Fun}, _Rec} <- SCC0], SelfRec = case SCC of [OneF] -> @@ -2717,12 +2727,14 @@ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes, Solvers) -> end; _Many -> false end, + PropTypes = dict:from_list(PropTypes0), #state{callgraph = CallGraph, name_map = NameMap, next_label = NextLabel, prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC), - mfas = MFAs, self_rec = SelfRec, solvers = Solvers}. + mfas = MFAs, self_rec = SelfRec, solvers = Solvers, + cserver = CServer}. -state__set_rec_dict(State, RecDict) -> - State#state{records = RecDict}. +state__set_module(State, Module) -> + State#state{module = Module}. state__set_in_match(State, Bool) -> State#state{in_match = Bool}. @@ -2832,7 +2844,7 @@ state__add_prop_constrs(Tree, #state{prop_types = PropTypes} = State) -> case erl_types:any_none(ArgTypes) of true -> not_called; false -> - ?debug("Adding propagated constr: ~s for function ~w\n", + ?debug("Adding propagated constr: ~ts for function ~tw\n", [format_type(FunType), debug_lookup_name(mk_var(Tree))]), FunVar = mk_var(Tree), state__store_conj(FunVar, sub, FunType, State) @@ -2906,8 +2918,9 @@ state__get_rec_var(Fun, #state{fun_map = Map}) -> maps:find(Fun, Map). state__finalize(State) -> - State1 = enumerate_constraints(State), - order_fun_constraints(State1). + State1 = state__new_constraint_context(State), + State2 = enumerate_constraints(State1), + order_fun_constraints(State2). %% ============================================================================ %% @@ -2960,6 +2973,11 @@ mk_fun_var(Line, Fun, Types) -> Deps = [t_var_name(Var) || Var <- t_collect_vars(t_product(Types))], #fun_var{'fun' = Fun, deps = ordsets:from_list(Deps), origin = Line}. +pp_map(S, Map) -> + ?debug("\t~s: ~p\n", + [S, [{X, lists:flatten(format_type(Y))} || + {X, Y} <- lists:keysort(1, maps:to_list(Map))]]). + -else. -spec mk_fun_var(fun((_) -> erl_types:erl_type()), [erl_types:erl_type()]) -> #fun_var{}. @@ -2987,7 +3005,7 @@ find_constraint_deps([Type|Tail], Acc) -> NewAcc = [[t_var_name(D) || D <- t_collect_vars(Type)]|Acc], find_constraint_deps(Tail, NewAcc); find_constraint_deps([], Acc) -> - lists:flatten(Acc). + lists:append(Acc). mk_constraint_1(Lhs, eq, Rhs, Deps) when Lhs < Rhs -> #constraint{lhs = Lhs, op = eq, rhs = Rhs, deps = Deps}; @@ -3095,8 +3113,8 @@ expand_to_conjunctions(#constraint_list{type = disj, list = List}) -> List1 = [C || C <- List, is_simple_constraint(C)], %% Just an assert. [] = [C || #constraint{} = C <- List1], - Expanded = lists:flatten([expand_to_conjunctions(C) - || #constraint_list{} = C <- List]), + Expanded = lists:append([expand_to_conjunctions(C) + || #constraint_list{} = C <- List]), ReturnList = Expanded ++ List1, if length(ReturnList) > ?DISJ_NORM_FORM_LIMIT -> throw(too_many_disj); true -> ReturnList @@ -3121,8 +3139,10 @@ calculate_deps(List) -> calculate_deps([H|Tail], Acc) -> Deps = get_deps(H), calculate_deps(Tail, [Deps|Acc]); +calculate_deps([], []) -> []; +calculate_deps([], [L]) -> L; calculate_deps([], Acc) -> - ordsets:from_list(lists:flatten(Acc)). + lists:umerge(Acc). mk_conj_constraint_list(List) -> mk_constraint_list(conj, List). @@ -3190,7 +3210,8 @@ order_fun_constraints(State) -> order_fun_constraints([#constraint_ref{id = Id}|Tail], State) -> Cs = state__get_cs(Id, State), - {[NewCs], State1} = order_fun_constraints([Cs], [], [], State), + {[Cs1], State1} = order_fun_constraints([Cs], [], [], State), + NewCs = Cs1#constraint_list{deps = Cs#constraint_list.deps}, NewState = state__store_constrs(Id, NewCs, State1), order_fun_constraints(Tail, NewState); order_fun_constraints([], State) -> @@ -3198,23 +3219,31 @@ order_fun_constraints([], State) -> order_fun_constraints([#constraint_ref{} = C|Tail], Funs, Acc, State) -> order_fun_constraints(Tail, [C|Funs], Acc, State); -order_fun_constraints([#constraint_list{list = List, type = Type} = C|Tail], +order_fun_constraints([#constraint_list{list = List, + type = Type, + masks = OldMasks} = C|Tail], Funs, Acc, State) -> - {NewList, NewState} = - case Type of - conj -> order_fun_constraints(List, [], [], State); - disj -> - FoldFun = fun(X, AccState) -> - {[NewX], NewAccState} = - order_fun_constraints([X], [], [], AccState), - {NewX, NewAccState} - end, - lists:mapfoldl(FoldFun, State, List) - end, - C1 = update_constraint_list(C, NewList), - Masks = calculate_masks(NewList, 1, []), - NewAcc = [update_masks(C1, Masks)|Acc], - order_fun_constraints(Tail, Funs, NewAcc, NewState); + case OldMasks of + undefined -> + {NewList, NewState} = + case Type of + conj -> order_fun_constraints(List, [], [], State); + disj -> + FoldFun = fun(X, AccState) -> + {[NewX], NewAccState} = + order_fun_constraints([X], [], [], AccState), + {NewX, NewAccState} + end, + lists:mapfoldl(FoldFun, State, List) + end, + NewList2 = reset_deps(NewList, State), + C1 = update_constraint_list(C, NewList2), + Masks = calculate_masks(NewList, 1, []), + NewAcc = [update_masks(C1, Masks)|Acc], + order_fun_constraints(Tail, Funs, NewAcc, NewState); + M when is_map(M) -> + order_fun_constraints(Tail, Funs, [C|Acc], State) + end; order_fun_constraints([#constraint{} = C|Tail], Funs, Acc, State) -> order_fun_constraints(Tail, Funs, [C|Acc], State); order_fun_constraints([], Funs, Acc, State) -> @@ -3224,6 +3253,18 @@ order_fun_constraints([], Funs, Acc, State) -> update_masks(C, Masks) -> C#constraint_list{masks = Masks}. +reset_deps(ConstrList, #state{solvers = Solvers}) -> + case lists:member(v1, Solvers) of + true -> + ConstrList; + false -> + [reset_deps(Constr) || Constr <- ConstrList] + end. + +reset_deps(#constraint{}=C) -> C#constraint{deps = []}; +reset_deps(#constraint_list{}=C) -> C#constraint_list{deps = []}; +reset_deps(#constraint_ref{}=C) -> C#constraint_ref{deps = []}. + calculate_masks([C|Cs], I, L0) -> calculate_masks(Cs, I+1, [{V, I} || V <- get_deps(C)] ++ L0); calculate_masks([], _I, L) -> @@ -3310,15 +3351,25 @@ fold_literal_maybe_match(Tree0, State) -> true -> dialyzer_utils:refold_pattern(Tree1) end. -lookup_record(Records, Tag, Arity) -> - case erl_types:lookup_record(Tag, Arity, Records) of +lookup_record(State, Tag, Arity) -> + #state{module = M, mod_records = ModRecs, cserver = CServer} = State, + {State1, Rec} = + case lists:keyfind(M, 1, ModRecs) of + {M, Rec0} -> + {State, Rec0}; + false -> + Rec0 = dialyzer_codeserver:lookup_mod_records(M, CServer), + NewModRecs = [{M, Rec0}|ModRecs], + {State#state{mod_records = NewModRecs}, Rec0} + end, + case erl_types:lookup_record(Tag, Arity, Rec) of {ok, Fields} -> RecType = t_tuple([t_from_term(Tag)| [FieldType || {_FieldName, _Abstr, FieldType} <- Fields]]), - {ok, RecType}; + {ok, RecType, State1}; error -> - error + {error, State1} end. is_literal_record(Tree) -> @@ -3343,10 +3394,10 @@ family(L) -> -ifdef(DEBUG). format_type(#fun_var{deps = Deps, origin = Origin}) -> L = [format_type(t_var(X)) || X <- Deps], - io_lib:format("Fun@L~p(~s)", [Origin, join_chars(L, ",")]); + io_lib:format("Fun@L~p(~ts)", [Origin, join_chars(L, ",")]); format_type(Type) -> case cerl:is_literal(Type) of - true -> io_lib:format("~w", [cerl:concrete(Type)]); + true -> io_lib:format("~tw", [cerl:concrete(Type)]); false -> erl_types:t_to_string(Type) end. @@ -3388,7 +3439,7 @@ pp_constrs_scc(SCC, State) -> [pp_constrs(Fun, state__get_cs(Fun, State), State) || Fun <- SCC]. pp_constrs(Fun, Cs, State) -> - io:format("Constraints for fun: ~w", [debug_lookup_name(Fun)]), + io:format("Constraints for fun: ~tw", [debug_lookup_name(Fun)]), MaxDepth = pp_constraints(Cs, State), io:format("Depth: ~w\n", [MaxDepth]). @@ -3426,7 +3477,7 @@ pp_constraints([#constraint_list{type = Type, list = List, id = Id}|Tail], pp_op(#constraint{lhs = Lhs, op = Op, rhs = Rhs}, Level) -> pp_indent(Level), - io:format("~s ~w ~s", [format_type(Lhs), Op, format_type(Rhs)]). + io:format("~ts ~w ~ts", [format_type(Lhs), Op, format_type(Rhs)]). pp_indent(Level) -> io:format("\n~*s", [Level*2, ""]). @@ -3438,19 +3489,25 @@ pp_constrs_scc(_SCC, _State) -> -ifdef(TO_DOT). constraints_to_dot_scc(SCC, State) -> - io:format("SCC: ~p\n", [SCC]), - Name = lists:flatten([io_lib:format("'~w'", [debug_lookup_name(Fun)]) - || Fun <- SCC]), + %% TODO: handle Unicode names. + io:format("SCC: ~tp\n", [SCC]), + Name = lists:flatten([format_lookup_name(debug_lookup_name(Fun)) + || Fun <- SCC]), Cs = [state__get_cs(Fun, State) || Fun <- SCC], constraints_to_dot(Cs, Name, State). +format_lookup_name({FunName, Arity}) -> + TupleS = io_lib:format("{~ts,~w}", [atom_to_list(FunName), Arity]), + io_lib:format("~tw", [list_to_atom(lists:flatten(TupleS))]). + constraints_to_dot(Cs0, Name, State) -> NofCs = length(Cs0), Cs = lists:zip(lists:seq(1, NofCs), Cs0), {Graph, Opts, _N} = constraints_to_nodes(Cs, NofCs + 1, 1, [], [], State), hipe_dot:translate_list(Graph, "/tmp/cs.dot", "foo", Opts), + %% "-T ps" works for Latin-1. hipe_dot cannot handle UTF-8 either. Res = os:cmd("dot -o /tmp/"++ Name ++ ".ps -T ps /tmp/cs.dot"), - io:format("Res: ~p~n", [Res]), + io:format("Res: ~ts~n", [Res]), ok. constraints_to_nodes([{Name, #constraint_list{type = Type, list = List, id=Id}} @@ -3469,7 +3526,7 @@ constraints_to_nodes([{Name, #constraint_list{type = Type, list = List, id=Id}} constraints_to_nodes(Left, N2, Level, NewGraph, NewOpts, State); constraints_to_nodes([{Name, #constraint{lhs = Lhs, op = Op, rhs = Rhs}}|Left], N, Level, Graph, Opts, State) -> - Label = lists:flatten(io_lib:format("~s ~w ~s", + Label = lists:flatten(io_lib:format("~ts ~w ~ts", [format_type(Lhs), Op, format_type(Rhs)])), ThisNode = [{Name, Opt} || Opt <- [{label, Label}, {level, Level}]], diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index 76a5cf3d0b..9b8fbc67eb 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% %%%------------------------------------------------------------------- %%% File : dialyzer_utils.erl @@ -31,25 +24,23 @@ -export([ format_sig/1, format_sig/2, - get_abstract_code_from_beam/1, - get_compile_options_from_beam/1, - get_abstract_code_from_src/1, - get_abstract_code_from_src/2, - get_core_from_abstract_code/1, - get_core_from_abstract_code/2, get_core_from_src/1, get_core_from_src/2, + get_core_from_beam/1, + get_core_from_beam/2, get_record_and_type_info/1, get_spec_info/3, get_fun_meta_info/3, is_suppressed_fun/2, is_suppressed_tag/3, - merge_records/2, pp_hook/0, process_record_remote_types/1, + merge_types/2, sets_filter/2, src_compiler_opts/0, refold_pattern/1, + ets_tab2list/1, + ets_move/2, parallelism/0, family/1 ]). @@ -67,15 +58,15 @@ print_types1([], _) -> ok; print_types1([{type, _Name, _NArgs} = Key|T], RecDict) -> {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict), - io:format("\n~w: ~w\n", [Key, Type]), + io:format("\n~tw: ~tw\n", [Key, Type]), print_types1(T, RecDict); print_types1([{opaque, _Name, _NArgs} = Key|T], RecDict) -> {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict), - io:format("\n~w: ~w\n", [Key, Type]), + io:format("\n~tw: ~tw\n", [Key, Type]), print_types1(T, RecDict); print_types1([{record, _Name} = Key|T], RecDict) -> {ok, {_FileLine, [{_Arity, _Fields} = AF]}} = dict:find(Key, RecDict), - io:format("~w: ~w\n\n", [Key, AF]), + io:format("~tw: ~tw\n\n", [Key, AF]), print_types1(T, RecDict). -define(debug(D_), print_types(D_)). -else. @@ -84,9 +75,7 @@ print_types1([{record, _Name} = Key|T], RecDict) -> %% ---------------------------------------------------------------------------- --type abstract_code() :: [erl_parse:abstract_form()]. -type comp_options() :: [compile:option()]. --type mod_or_fname() :: module() | file:filename(). -type fa() :: {atom(), arity()}. -type codeserver() :: dialyzer_codeserver:codeserver(). @@ -96,63 +85,82 @@ print_types1([{record, _Name} = Key|T], RecDict) -> %% %% ============================================================================ --spec get_abstract_code_from_src(mod_or_fname()) -> - {'ok', abstract_code()} | {'error', [string()]}. +-type get_core_from_src_ret() :: {'ok', cerl:c_module()} | {'error', string()}. + +-spec get_core_from_src(file:filename()) -> get_core_from_src_ret(). -get_abstract_code_from_src(File) -> - get_abstract_code_from_src(File, src_compiler_opts()). +get_core_from_src(File) -> + get_core_from_src(File, []). --spec get_abstract_code_from_src(mod_or_fname(), comp_options()) -> - {'ok', abstract_code()} | {'error', [string()]}. +-spec get_core_from_src(file:filename(), comp_options()) -> get_core_from_src_ret(). -get_abstract_code_from_src(File, Opts) -> - case compile:noenv_file(File, [to_pp, binary|Opts]) of +get_core_from_src(File, Opts) -> + case compile:noenv_file(File, Opts ++ src_compiler_opts()) of error -> {error, []}; {error, Errors, _} -> {error, format_errors(Errors)}; - {ok, _, AbstrCode} -> {ok, AbstrCode} + {ok, _, Core} -> {ok, Core} end. --type get_core_from_src_ret() :: {'ok', cerl:c_module()} | {'error', string()}. +-type get_core_from_beam_ret() :: {'ok', cerl:c_module()} | {'error', string()}. --spec get_core_from_src(file:filename()) -> get_core_from_src_ret(). +-spec get_core_from_beam(file:filename()) -> get_core_from_beam_ret(). -get_core_from_src(File) -> - get_core_from_src(File, []). +get_core_from_beam(File) -> + get_core_from_beam(File, []). --spec get_core_from_src(file:filename(), comp_options()) -> get_core_from_src_ret(). +-spec get_core_from_beam(file:filename(), comp_options()) -> get_core_from_beam_ret(). -get_core_from_src(File, Opts) -> - case get_abstract_code_from_src(File, Opts) of - {error, _} = Error -> Error; +get_core_from_beam(File, Opts) -> + case beam_lib:chunks(File, [debug_info]) of + {ok, {Module, [{debug_info, {debug_info_v1, Backend, Metadata}}]}} -> + case Backend:debug_info(core_v1, Module, Metadata, Opts ++ src_compiler_opts()) of + {ok, Core} -> + {ok, Core}; + {error, _} -> + {error, " Could not get Core Erlang code for: " ++ File ++ "\n"} + end; + _ -> + deprecated_get_core_from_beam(File, Opts) + end. + +deprecated_get_core_from_beam(File, Opts) -> + case get_abstract_code_from_beam(File) of + error -> + {error, " Could not get abstract code for: " ++ File ++ "\n" ++ + " Recompile with +debug_info or analyze starting from source code"}; {ok, AbstrCode} -> - case get_core_from_abstract_code(AbstrCode, Opts) of - error -> {error, " Could not get Core Erlang code from abstract code"}; - {ok, _Core} = C -> C + case get_compile_options_from_beam(File) of + error -> + {error, " Could not get compile options for: " ++ File ++ "\n" ++ + " Recompile or analyze starting from source code"}; + {ok, CompOpts} -> + case get_core_from_abstract_code(AbstrCode, Opts ++ CompOpts) of + error -> + {error, " Could not get core Erlang code for: " ++ File}; + {ok, _} = Core -> + Core + end end end. --spec get_abstract_code_from_beam(file:filename()) -> 'error' | {'ok', abstract_code()}. - get_abstract_code_from_beam(File) -> case beam_lib:chunks(File, [abstract_code]) of {ok, {_, List}} -> case lists:keyfind(abstract_code, 1, List) of - {abstract_code, {raw_abstract_v1, Abstr}} -> {ok, Abstr}; - _ -> error + {abstract_code, {raw_abstract_v1, Abstr}} -> {ok, Abstr}; + _ -> error end; _ -> %% No or unsuitable abstract code. error end. --spec get_compile_options_from_beam(file:filename()) -> 'error' | {'ok', [compile:option()]}. - get_compile_options_from_beam(File) -> case beam_lib:chunks(File, [compile_info]) of {ok, {_, List}} -> case lists:keyfind(compile_info, 1, List) of - {compile_info, CompInfo} -> compile_info_to_options(CompInfo); - _ -> error + {compile_info, CompInfo} -> compile_info_to_options(CompInfo); + _ -> error end; _ -> %% No or unsuitable compile info. @@ -165,15 +173,6 @@ compile_info_to_options(CompInfo) -> _ -> error end. --type get_core_from_abs_ret() :: {'ok', cerl:c_module()} | 'error'. - --spec get_core_from_abstract_code(abstract_code()) -> get_core_from_abs_ret(). - -get_core_from_abstract_code(AbstrCode) -> - get_core_from_abstract_code(AbstrCode, []). - --spec get_core_from_abstract_code(abstract_code(), comp_options()) -> get_core_from_abs_ret(). - get_core_from_abstract_code(AbstrCode, Opts) -> %% We do not want the parse_transforms around since we already %% performed them. In some cases we end up in trouble when @@ -182,12 +181,31 @@ get_core_from_abstract_code(AbstrCode, Opts) -> %% Remove parse_transforms (and other options) from compile options. Opts2 = cleanup_compile_options(Opts), try compile:noenv_forms(AbstrCode1, Opts2 ++ src_compiler_opts()) of - {ok, _, Core} -> {ok, Core}; - _What -> error + {ok, _, Core} -> {ok, Core}; + _What -> error catch error:_ -> error end. +cleanup_parse_transforms([{attribute, _, compile, {parse_transform, _}}|Left]) -> + cleanup_parse_transforms(Left); +cleanup_parse_transforms([Other|Left]) -> + [Other|cleanup_parse_transforms(Left)]; +cleanup_parse_transforms([]) -> + []. + +cleanup_compile_options(Opts) -> + lists:filter(fun keep_compile_option/1, Opts). + +%% Using abstract, not asm or core. +keep_compile_option(from_asm) -> false; +keep_compile_option(from_core) -> false; +%% The parse transform will already have been applied, may cause +%% problems if it is re-applied. +keep_compile_option({parse_transform, _}) -> false; +keep_compile_option(warnings_as_errors) -> false; +keep_compile_option(_) -> true. + %% ============================================================================ %% %% Typed Records @@ -195,57 +213,51 @@ get_core_from_abstract_code(AbstrCode, Opts) -> %% ============================================================================ -type type_table() :: erl_types:type_table(). --type mod_records() :: dict:dict(module(), type_table()). --spec get_record_and_type_info(abstract_code()) -> - {'ok', type_table()} | {'error', string()}. +-spec get_record_and_type_info(cerl:c_module()) -> + {'ok', type_table()} | {'error', string()}. -get_record_and_type_info(AbstractCode) -> - Module = get_module(AbstractCode), - get_record_and_type_info(AbstractCode, Module, dict:new()). +get_record_and_type_info(Core) -> + Module = cerl:concrete(cerl:module_name(Core)), + Tuples = core_to_attr_tuples(Core), + get_record_and_type_info(Tuples, Module, maps:new(), "nofile"). --spec get_record_and_type_info(abstract_code(), module(), type_table()) -> - {'ok', type_table()} | {'error', string()}. - -get_record_and_type_info(AbstractCode, Module, RecDict) -> - get_record_and_type_info(AbstractCode, Module, RecDict, "nofile"). - -get_record_and_type_info([{attribute, A, record, {Name, Fields0}}|Left], +get_record_and_type_info([{record, Line, [{Name, Fields0}]}|Left], Module, RecDict, File) -> {ok, Fields} = get_record_fields(Fields0, RecDict), Arity = length(Fields), - FN = {File, erl_anno:line(A)}, - NewRecDict = dict:store({record, Name}, {FN, [{Arity,Fields}]}, RecDict), + FN = {File, Line}, + NewRecDict = maps:put({record, Name}, {FN, [{Arity,Fields}]}, RecDict), get_record_and_type_info(Left, Module, NewRecDict, File); -get_record_and_type_info([{attribute, A, type, {{record, Name}, Fields0, []}} +get_record_and_type_info([{type, Line, [{{record, Name}, Fields0, []}]} |Left], Module, RecDict, File) -> %% This overrides the original record declaration. {ok, Fields} = get_record_fields(Fields0, RecDict), Arity = length(Fields), - FN = {File, erl_anno:line(A)}, - NewRecDict = dict:store({record, Name}, {FN, [{Arity, Fields}]}, RecDict), + FN = {File, Line}, + NewRecDict = maps:put({record, Name}, {FN, [{Arity, Fields}]}, RecDict), get_record_and_type_info(Left, Module, NewRecDict, File); -get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm}}|Left], +get_record_and_type_info([{Attr, Line, [{Name, TypeForm}]}|Left], Module, RecDict, File) when Attr =:= 'type'; Attr =:= 'opaque' -> - FN = {File, erl_anno:line(A)}, + FN = {File, Line}, try add_new_type(Attr, Name, TypeForm, [], Module, FN, RecDict) of NewRecDict -> get_record_and_type_info(Left, Module, NewRecDict, File) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm, Args}}|Left], +get_record_and_type_info([{Attr, Line, [{Name, TypeForm, Args}]}|Left], Module, RecDict, File) when Attr =:= 'type'; Attr =:= 'opaque' -> - FN = {File, erl_anno:line(A)}, + FN = {File, Line}, try add_new_type(Attr, Name, TypeForm, Args, Module, FN, RecDict) of NewRecDict -> get_record_and_type_info(Left, Module, NewRecDict, File) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([{attribute, _, file, {IncludeFile, _}}|Left], +get_record_and_type_info([{file, _, [{IncludeFile, _}]}|Left], Module, RecDict, _File) -> get_record_and_type_info(Left, Module, RecDict, IncludeFile); get_record_and_type_info([_Other|Left], Module, RecDict, File) -> @@ -258,17 +270,17 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, FN, Arity = length(ArgForms), case erl_types:type_is_defined(TypeOrOpaque, Name, Arity, RecDict) of true -> - Msg = flat_format("Type ~s/~w already defined\n", [Name, Arity]), + Msg = flat_format("Type ~ts/~w already defined\n", [Name, Arity]), throw({error, Msg}); false -> try erl_types:t_var_names(ArgForms) of ArgNames -> - dict:store({TypeOrOpaque, Name, Arity}, - {{Module, FN, TypeForm, ArgNames}, - erl_types:t_any()}, RecDict) + maps:put({TypeOrOpaque, Name, Arity}, + {{Module, FN, TypeForm, ArgNames}, + erl_types:t_any()}, RecDict) catch _:_ -> - throw({error, flat_format("Type declaration for ~w does not " + throw({error, flat_format("Type declaration for ~tw does not " "have variables as parameters", [Name])}) end end. @@ -300,28 +312,30 @@ get_record_fields([], _RecDict, Acc) -> %% The field types are cached. Used during analysis when handling records. process_record_remote_types(CServer) -> - TempRecords = dialyzer_codeserver:get_temp_records(CServer), ExpTypes = dialyzer_codeserver:get_exported_types(CServer), - Cache = erl_types:cache__new(), - {TempRecords1, Cache1} = - process_opaque_types0(TempRecords, ExpTypes, Cache), - %% A cache (not the field type cache) is used for speeding things up a bit. + Mods = dialyzer_codeserver:all_temp_modules(CServer), + process_opaque_types0(Mods, CServer, ExpTypes), VarTable = erl_types:var_table__new(), + RecordTable = dialyzer_codeserver:get_temp_records_table(CServer), ModuleFun = - fun({Module, Record}, C0) -> + fun(Module) -> + RecordMap = dialyzer_codeserver:lookup_temp_mod_records(Module, CServer), RecordFun = fun({Key, Value}, C2) -> case Key of {record, Name} -> FieldFun = fun({Arity, Fields}, C4) -> - Site = {record, {Module, Name, Arity}}, + MRA = {Module, Name, Arity}, + Site = {record, MRA}, {Fields1, C7} = lists:mapfoldl(fun({FieldName, Field, _}, C5) -> + check_remote(Field, ExpTypes, + MRA, RecordTable), {FieldT, C6} = erl_types:t_from_form (Field, ExpTypes, Site, - TempRecords1, VarTable, + RecordTable, VarTable, C5), {{FieldName, Field, FieldT}, C6} end, C4, Fields), @@ -331,32 +345,40 @@ process_record_remote_types(CServer) -> {FieldsList, C3} = lists:mapfoldl(FieldFun, C2, orddict:to_list(Fields)), {{Key, {FileLine, orddict:from_list(FieldsList)}}, C3}; - _Other -> {{Key, Value}, C2} + {_TypeOrOpaque, Name, NArgs} -> + %% Make sure warnings about unknown types are output + %% also for types unused by specs. + MTA = {Module, Name, NArgs}, + {{_Module, _FileLine, Form, _ArgNames}, _Type} = Value, + check_remote(Form, ExpTypes, MTA, RecordTable), + {{Key, Value}, C2} end end, - {RecordList, C1} = - lists:mapfoldl(RecordFun, C0, dict:to_list(Record)), - {{Module, dict:from_list(RecordList)}, C1} + Cache = erl_types:cache__new(), + {RecordList, _NewCache} = + lists:mapfoldl(RecordFun, Cache, maps:to_list(RecordMap)), + dialyzer_codeserver:store_temp_records(Module, + maps:from_list(RecordList), + CServer) end, - {NewRecordsList, C1} = - lists:mapfoldl(ModuleFun, Cache1, dict:to_list(TempRecords1)), - NewRecords = dict:from_list(NewRecordsList), - _C8 = check_record_fields(NewRecords, ExpTypes, C1), - dialyzer_codeserver:finalize_records(NewRecords, CServer). + lists:foreach(ModuleFun, Mods), + check_record_fields(Mods, CServer, ExpTypes), + dialyzer_codeserver:finalize_records(CServer). %% erl_types:t_from_form() substitutes the declaration of opaque types %% for the expanded type in some cases. To make sure the initial type, %% any(), is not used, the expansion is done twice. %% XXX: Recursive opaque types are not handled well. -process_opaque_types0(TempRecords0, TempExpTypes, Cache) -> - {TempRecords1, NewCache} = - process_opaque_types(TempRecords0, TempExpTypes, Cache), - process_opaque_types(TempRecords1, TempExpTypes, NewCache). +process_opaque_types0(AllModules, CServer, TempExpTypes) -> + process_opaque_types(AllModules, CServer, TempExpTypes), + process_opaque_types(AllModules, CServer, TempExpTypes). -process_opaque_types(TempRecords, TempExpTypes, Cache) -> +process_opaque_types(AllModules, CServer, TempExpTypes) -> VarTable = erl_types:var_table__new(), + RecordTable = dialyzer_codeserver:get_temp_records_table(CServer), ModuleFun = - fun({Module, Record}, C0) -> + fun(Module) -> + RecordMap = dialyzer_codeserver:lookup_temp_mod_records(Module, CServer), RecordFun = fun({Key, Value}, C2) -> case Key of @@ -365,31 +387,35 @@ process_opaque_types(TempRecords, TempExpTypes, Cache) -> Site = {type, {Module, Name, NArgs}}, {Type, C3} = erl_types:t_from_form(Form, TempExpTypes, Site, - TempRecords, VarTable, C2), + RecordTable, VarTable, C2), {{Key, {F, Type}}, C3}; - _Other -> {{Key, Value}, C2} + {type, _Name, _NArgs} -> + {{Key, Value}, C2}; + {record, _RecName} -> + {{Key, Value}, C2} end end, - {RecordList, C1} = - lists:mapfoldl(RecordFun, C0, dict:to_list(Record)), - {{Module, dict:from_list(RecordList)}, C1} - %% dict:map(RecordFun, Record) + C0 = erl_types:cache__new(), + {RecordList, _NewCache} = + lists:mapfoldl(RecordFun, C0, maps:to_list(RecordMap)), + dialyzer_codeserver:store_temp_records(Module, + maps:from_list(RecordList), + CServer) end, - {TempRecordList, NewCache} = - lists:mapfoldl(ModuleFun, Cache, dict:to_list(TempRecords)), - {dict:from_list(TempRecordList), NewCache}. - %% dict:map(ModuleFun, TempRecords). + lists:foreach(ModuleFun, AllModules). -check_record_fields(Records, TempExpTypes, Cache) -> +check_record_fields(AllModules, CServer, TempExpTypes) -> VarTable = erl_types:var_table__new(), + RecordTable = dialyzer_codeserver:get_temp_records_table(CServer), CheckFun = - fun({Module, Element}, C0) -> + fun(Module) -> CheckForm = fun(Form, Site, C1) -> erl_types:t_check_record_fields(Form, TempExpTypes, - Site, Records, + Site, RecordTable, VarTable, C1) end, - ElemFun = + RecordMap = dialyzer_codeserver:lookup_temp_mod_records(Module, CServer), + RecordFun = fun({Key, Value}, C2) -> case Key of {record, Name} -> @@ -410,9 +436,10 @@ check_record_fields(Records, TempExpTypes, Cache) -> msg_with_position(Fun, FileLine) end end, - lists:foldl(ElemFun, C0, dict:to_list(Element)) + C0 = erl_types:cache__new(), + _ = lists:foldl(RecordFun, C0, maps:to_list(RecordMap)) end, - lists:foldl(CheckFun, Cache, dict:to_list(Records)). + lists:foreach(CheckFun, AllModules). msg_with_position(Fun, FileLine) -> try Fun() @@ -420,14 +447,44 @@ msg_with_position(Fun, FileLine) -> throw:{error, Msg} -> {File, Line} = FileLine, BaseName = filename:basename(File), - NewMsg = io_lib:format("~s:~p: ~s", [BaseName, Line, Msg]), + NewMsg = io_lib:format("~ts:~p: ~ts", [BaseName, Line, Msg]), throw({error, NewMsg}) end. --spec merge_records(mod_records(), mod_records()) -> mod_records(). +check_remote(Form, ExpTypes, What, RecordTable) -> + erl_types:t_from_form_check_remote(Form, ExpTypes, What, RecordTable). + +-spec merge_types(codeserver(), dialyzer_plt:plt()) -> codeserver(). -merge_records(NewRecords, OldRecords) -> - dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords). +merge_types(CServer, Plt) -> + AllNewModules = dialyzer_codeserver:all_temp_modules(CServer), + AllNewModulesSet = sets:from_list(AllNewModules), + AllOldModulesSet = dialyzer_plt:all_modules(Plt), + AllModulesSet = sets:union(AllNewModulesSet, AllOldModulesSet), + ModuleFun = + fun(Module) -> + KeepOldFun = + fun() -> + case dialyzer_plt:get_module_types(Plt, Module) of + none -> no; + {value, OldRecords} -> + case sets:is_element(Module, AllNewModulesSet) of + true -> no; + false -> {yes, OldRecords} + end + end + end, + Records = + case KeepOldFun() of + no -> + dialyzer_codeserver:lookup_temp_mod_records(Module, CServer); + {yes, OldRecords} -> + OldRecords + end, + dialyzer_codeserver:store_temp_records(Module, Records, CServer) + end, + lists:foreach(ModuleFun, sets:to_list(AllModulesSet)), + CServer. %% ============================================================================ %% @@ -435,26 +492,21 @@ merge_records(NewRecords, OldRecords) -> %% %% ============================================================================ --type spec_dict() :: dict:dict(). --type callback_dict() :: dict:dict(). +-type spec_map() :: dialyzer_codeserver:contracts(). +-type callback_map() :: dialyzer_codeserver:contracts(). --spec get_spec_info(module(), abstract_code(), type_table()) -> - {'ok', spec_dict(), callback_dict()} | {'error', string()}. +-spec get_spec_info(module(), cerl:c_module(), type_table()) -> + {'ok', spec_map(), callback_map()} | {'error', string()}. -get_spec_info(ModName, AbstractCode, RecordsDict) -> - OptionalCallbacks0 = get_optional_callbacks(AbstractCode, ModName), +get_spec_info(ModName, Core, RecordsMap) -> + Tuples = core_to_attr_tuples(Core), + OptionalCallbacks0 = get_optional_callbacks(Tuples, ModName), OptionalCallbacks = gb_sets:from_list(OptionalCallbacks0), - get_spec_info(AbstractCode, dict:new(), dict:new(), - RecordsDict, ModName, OptionalCallbacks, "nofile"). - -get_optional_callbacks(Abs, ModName) -> - [{ModName, F, A} || {F, A} <- get_optional_callbacks(Abs)]. + get_spec_info(Tuples, maps:new(), maps:new(), + RecordsMap, ModName, OptionalCallbacks, "nofile"). -get_optional_callbacks(Abs) -> - L = [O || - {attribute, _, optional_callbacks, O} <- Abs, - is_fa_list(O)], - lists:append(L). +get_optional_callbacks(Tuples, ModName) -> + [{ModName, F, A} || {optional_callbacks, _, O} <- Tuples, is_fa_list(O), {F, A} <- O]. %% TypeSpec is a list of conditional contracts for a function. %% Each contract is of the form {[Argument], Range, [Constraint]} where @@ -462,68 +514,82 @@ get_optional_callbacks(Abs) -> %% - Constraint is of the form {subtype, T1, T2} where T1 and T2 %% are erl_types:erl_type() -get_spec_info([{attribute, Anno, Contract, {Id, TypeSpec}}|Left], - SpecDict, CallbackDict, RecordsDict, ModName, OptCb, File) +get_spec_info([{Contract, Ln, [{Id, TypeSpec}]}|Left], + SpecMap, CallbackMap, RecordsMap, ModName, OptCb, File) when ((Contract =:= 'spec') or (Contract =:= 'callback')), is_list(TypeSpec) -> - Ln = erl_anno:line(Anno), MFA = case Id of {_, _, _} = T -> T; {F, A} -> {ModName, F, A} end, Xtra = [optional_callback || gb_sets:is_member(MFA, OptCb)], - ActiveDict = + ActiveMap = case Contract of - spec -> SpecDict; - callback -> CallbackDict + spec -> SpecMap; + callback -> CallbackMap end, - try dict:find(MFA, ActiveDict) of + try maps:find(MFA, ActiveMap) of error -> SpecData = {TypeSpec, Xtra}, - NewActiveDict = + NewActiveMap = dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, SpecData, - ActiveDict, RecordsDict), - {NewSpecDict, NewCallbackDict} = + ActiveMap, RecordsMap), + {NewSpecMap, NewCallbackMap} = case Contract of - spec -> {NewActiveDict, CallbackDict}; - callback -> {SpecDict, NewActiveDict} + spec -> {NewActiveMap, CallbackMap}; + callback -> {SpecMap, NewActiveMap} end, - get_spec_info(Left, NewSpecDict, NewCallbackDict, - RecordsDict, ModName, OptCb, File); + get_spec_info(Left, NewSpecMap, NewCallbackMap, + RecordsMap, ModName, OptCb, File); {ok, {{OtherFile, L}, _D}} -> {Mod, Fun, Arity} = MFA, - Msg = flat_format(" Contract/callback for function ~w:~w/~w " - "already defined in ~s:~w\n", + Msg = flat_format(" Contract/callback for function ~w:~tw/~w " + "already defined in ~ts:~w\n", [Mod, Fun, Arity, OtherFile, L]), throw({error, Msg}) catch throw:{error, Error} -> - {error, flat_format(" Error while parsing contract in line ~w: ~s\n", + {error, flat_format(" Error while parsing contract in line ~w: ~ts\n", [Ln, Error])} end; -get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left], - SpecDict, CallbackDict, RecordsDict, ModName, OptCb, _File) -> - get_spec_info(Left, SpecDict, CallbackDict, - RecordsDict, ModName, OptCb, IncludeFile); -get_spec_info([_Other|Left], SpecDict, CallbackDict, - RecordsDict, ModName, OptCb, File) -> - get_spec_info(Left, SpecDict, CallbackDict, - RecordsDict, ModName, OptCb, File); -get_spec_info([], SpecDict, CallbackDict, - _RecordsDict, _ModName, _OptCb, _File) -> - {ok, SpecDict, CallbackDict}. - --spec get_fun_meta_info(module(), abstract_code(), [dial_warn_tag()]) -> - dialyzer_codeserver:fun_meta_info(). - -get_fun_meta_info(M, Abs, LegalWarnings) -> - NoWarn = get_nowarn_unused_function(M, Abs), - FuncSupp = get_func_suppressions(M, Abs), - Warnings0 = get_options(Abs, LegalWarnings), - Warnings = ordsets:to_list(Warnings0), - ModuleWarnings = [{M, W} || W <- Warnings], - RawProps = lists:append([NoWarn, FuncSupp, ModuleWarnings]), - process_options(dialyzer_utils:family(RawProps), Warnings0). +get_spec_info([{file, _, [{IncludeFile, _}]}|Left], + SpecMap, CallbackMap, RecordsMap, ModName, OptCb, _File) -> + get_spec_info(Left, SpecMap, CallbackMap, + RecordsMap, ModName, OptCb, IncludeFile); +get_spec_info([_Other|Left], SpecMap, CallbackMap, + RecordsMap, ModName, OptCb, File) -> + get_spec_info(Left, SpecMap, CallbackMap, + RecordsMap, ModName, OptCb, File); +get_spec_info([], SpecMap, CallbackMap, + _RecordsMap, _ModName, _OptCb, _File) -> + {ok, SpecMap, CallbackMap}. + +core_to_attr_tuples(Core) -> + [{cerl:concrete(Key), get_core_line(cerl:get_ann(Key)), cerl:concrete(Value)} || + {Key, Value} <- cerl:module_attrs(Core)]. + +get_core_line([L | _As]) when is_integer(L) -> L; +get_core_line([_ | As]) -> get_core_line(As); +get_core_line([]) -> undefined. + +-spec get_fun_meta_info(module(), cerl:c_module(), [dial_warn_tag()]) -> + dialyzer_codeserver:fun_meta_info() | {'error', string()}. + +get_fun_meta_info(M, Core, LegalWarnings) -> + Functions = lists:map(fun cerl:var_name/1, cerl:module_vars(Core)), + try + {get_nowarn_unused_function(M, Core, Functions), + get_func_suppressions(M, Core, Functions)} + of + {NoWarn, FuncSupp} -> + Warnings0 = get_options(Core, LegalWarnings), + Warnings = ordsets:to_list(Warnings0), + ModuleWarnings = [{M, W} || W <- Warnings], + RawProps = lists:append([NoWarn, FuncSupp, ModuleWarnings]), + process_options(dialyzer_utils:family(RawProps), Warnings0) + catch throw:{error, _} = Error -> + Error + end. process_options([{M, _}=Mod|Left], Warnings) when is_atom(M) -> [Mod|process_options(Left, Warnings)]; @@ -542,74 +608,75 @@ process_options([{{_M, _F, _A}=MFA, Opts}|Left], Warnings) -> end; process_options([], _Warnings) -> []. --spec get_nowarn_unused_function(module(), abstract_code()) -> +-spec get_nowarn_unused_function(module(), cerl:c_module(), [fa()]) -> [{mfa(), 'no_unused'}]. -get_nowarn_unused_function(M, Abs) -> - Opts = get_options_with_tag(compile, Abs), +get_nowarn_unused_function(M, Core, Functions) -> + Opts = get_options_with_tag(compile, Core), Warn = erl_lint:bool_option(warn_unused_function, nowarn_unused_function, true, Opts), - Functions = [{F, A} || {function, _, F, A, _} <- Abs], - AttrFile = collect_attribute(Abs, compile), + AttrFile = collect_attribute(Core, compile), TagsFaList = check_fa_list(AttrFile, nowarn_unused_function, Functions), FAs = case Warn of false -> Functions; true -> - [FA || {{nowarn_unused_function,_L,_File}, FA} <- TagsFaList] + [FA || {{[nowarn_unused_function],_L,_File}, FA} <- TagsFaList] end, [{{M, F, A}, no_unused} || {F, A} <- FAs]. --spec get_func_suppressions(module(), abstract_code()) -> +-spec get_func_suppressions(module(), cerl:c_module(), [fa()]) -> [{mfa(), 'nowarn_function' | dial_warn_tag()}]. -get_func_suppressions(M, Abs) -> - Functions = [{F, A} || {function, _, F, A, _} <- Abs], - AttrFile = collect_attribute(Abs, dialyzer), +get_func_suppressions(M, Core, Functions) -> + AttrFile = collect_attribute(Core, dialyzer), TagsFAs = check_fa_list(AttrFile, '*', Functions), %% Check the options: - Fun = fun({{nowarn_function, _L, _File}, _FA}) -> ok; + Fun = fun({{[nowarn_function], _L, _File}, _FA}) -> ok; ({OptLFile, _FA}) -> _ = get_options1([OptLFile], ordsets:new()) end, lists:foreach(Fun, TagsFAs), - [{{M, F, A}, W} || {{W, _L, _File}, {F, A}} <- TagsFAs]. + [{{M, F, A}, W} || {{Warnings, _L, _File}, {F, A}} <- TagsFAs, W <- Warnings]. --spec get_options(abstract_code(), [dial_warn_tag()]) -> +-spec get_options(cerl:c_module(), [dial_warn_tag()]) -> ordsets:ordset(dial_warn_tag()). -get_options(Abs, LegalWarnings) -> - AttrFile = collect_attribute(Abs, dialyzer), +get_options(Core, LegalWarnings) -> + AttrFile = collect_attribute(Core, dialyzer), get_options1(AttrFile, LegalWarnings). get_options1([{Args, L, File}|Left], Warnings) -> - Opts = [O || - O <- lists:flatten([Args]), - is_atom(O)], + Opts = [O || O <- Args, is_atom(O)], try dialyzer_options:build_warnings(Opts, Warnings) of NewWarnings -> get_options1(Left, NewWarnings) catch throw:{dialyzer_options_error, Msg} -> - Msg1 = flat_format(" ~s:~w: ~s", [File, L, Msg]), + Msg1 = flat_format(" ~ts:~w: ~ts", [File, L, Msg]), throw({error, Msg1}) end; get_options1([], Warnings) -> Warnings. -type collected_attribute() :: - {Args :: term(), erl_anno:line(), file:filename()}. - -collect_attribute(Abs, Tag) -> - collect_attribute(Abs, Tag, "nofile"). - -collect_attribute([{attribute, L, Tag, Args}|Left], Tag, File) -> - CollAttr = {Args, L, File}, - [CollAttr | collect_attribute(Left, Tag, File)]; -collect_attribute([{attribute, _, file, {IncludeFile, _}}|Left], Tag, _) -> - collect_attribute(Left, Tag, IncludeFile); -collect_attribute([_Other|Left], Tag, File) -> - collect_attribute(Left, Tag, File); -collect_attribute([], _Tag, _File) -> []. + {Args :: [term()], erl_anno:line(), file:filename()}. + +collect_attribute(Core, Tag) -> + collect_attribute(cerl:module_attrs(Core), Tag, "nofile"). + +collect_attribute([{Key, Value}|T], Tag, File) -> + case cerl:concrete(Key) of + Tag -> + [{cerl:concrete(Value), get_core_line(cerl:get_ann(Key)), File} | + collect_attribute(T, Tag, File)]; + file -> + [{IncludeFile, _}] = cerl:concrete(Value), + collect_attribute(T, Tag, IncludeFile); + _ -> + collect_attribute(T, Tag, File) + end; +collect_attribute([], _Tag, _File) -> + []. -spec is_suppressed_fun(mfa(), codeserver()) -> boolean(). @@ -660,40 +727,11 @@ src_compiler_opts() -> no_inline, strict_record_tests, strict_record_updates, dialyzer]. --spec get_module(abstract_code()) -> module(). - -get_module([{attribute, _, module, {M, _As}} | _]) -> M; -get_module([{attribute, _, module, M} | _]) -> M; -get_module([_ | Rest]) -> get_module(Rest). - --spec cleanup_parse_transforms(abstract_code()) -> abstract_code(). - -cleanup_parse_transforms([{attribute, _, compile, {parse_transform, _}}|Left]) -> - cleanup_parse_transforms(Left); -cleanup_parse_transforms([Other|Left]) -> - [Other|cleanup_parse_transforms(Left)]; -cleanup_parse_transforms([]) -> - []. - --spec cleanup_compile_options([compile:option()]) -> [compile:option()]. - -cleanup_compile_options(Opts) -> - lists:filter(fun keep_compile_option/1, Opts). - -%% Using abstract, not asm or core. -keep_compile_option(from_asm) -> false; -keep_compile_option(from_core) -> false; -%% The parse transform will already have been applied, may cause -%% problems if it is re-applied. -keep_compile_option({parse_transform, _}) -> false; -keep_compile_option(warnings_as_errors) -> false; -keep_compile_option(_) -> true. - -spec format_errors([{module(), string()}]) -> [string()]. format_errors([{Mod, Errors}|Left]) -> FormatedError = - [io_lib:format("~s:~w: ~s\n", [Mod, Line, M:format_error(Desc)]) + [io_lib:format("~ts:~w: ~ts\n", [Mod, Line, M:format_error(Desc)]) || {Line, M, Desc} <- Errors], [lists:flatten(FormatedError) | format_errors(Left)]; format_errors([]) -> @@ -702,7 +740,7 @@ format_errors([]) -> -spec format_sig(erl_types:erl_type()) -> string(). format_sig(Type) -> - format_sig(Type, dict:new()). + format_sig(Type, maps:new()). -spec format_sig(erl_types:erl_type(), type_table()) -> string(). @@ -714,10 +752,12 @@ format_sig(Type, RecDict) -> flat_format(Fmt, Lst) -> lists:flatten(io_lib:format(Fmt, Lst)). --spec get_options_with_tag(atom(), abstract_code()) -> [term()]. +-spec get_options_with_tag(atom(), cerl:c_module()) -> [term()]. -get_options_with_tag(Tag, Abs) -> - lists:flatten([O || {attribute, _, Tag0, O} <- Abs, Tag =:= Tag0]). +get_options_with_tag(Tag, Core) -> + [O || {Key, Value} <- cerl:module_attrs(Core), + cerl:concrete(Key) =:= Tag, + O <- cerl:concrete(Value)]. %% Check F/A, and collect (unchecked) warning tags with line and file. -spec check_fa_list([collected_attribute()], atom(), [fa()]) -> @@ -728,22 +768,22 @@ check_fa_list(AttrFile, Tag, Functions) -> check_fa_list1(AttrFile, Tag, FuncTab). check_fa_list1([{Args, L, File}|Left], Tag, Funcs) -> - TermsL = [{{Tag0, L, File}, Term} || - {Tags, Terms0} <- lists:flatten([Args]), + TermsL = [{{[Tag0], L, File}, Term} || + {Tags, Terms0} <- Args, Tag0 <- lists:flatten([Tags]), Tag =:= '*' orelse Tag =:= Tag0, Term <- lists:flatten([Terms0])], case lists:dropwhile(fun({_, T}) -> is_fa(T) end, TermsL) of [] -> ok; [{_, Bad}|_] -> - Msg1 = flat_format(" Bad function ~w in line ~s:~w", + Msg1 = flat_format(" Bad function ~tw in line ~ts:~w", [Bad, File, L]), throw({error, Msg1}) end, case lists:dropwhile(fun({_, FA}) -> is_known(FA, Funcs) end, TermsL) of [] -> ok; [{_, {F, A}}|_] -> - Msg2 = flat_format(" Unknown function ~w/~w in line ~s:~w", + Msg2 = flat_format(" Unknown function ~tw/~w in line ~ts:~w", [F, A, File, L]), throw({error, Msg2}) end, @@ -951,12 +991,39 @@ label(Tree) -> %%------------------------------------------------------------------------------ +-spec ets_tab2list(ets:tid()) -> list(). + +%% Deletes the contents of the table. Use: +%% ets_tab2list(T), ets:delete(T) +%% instead of: +%% ets:tab2list(T), ets:delete(T) +%% to save some memory at the expense of somewhat longer execution time. +ets_tab2list(T) -> + F = fun(Vs, A) -> Vs ++ A end, + ets_take(ets:first(T), T, F, []). + +-spec ets_move(From :: ets:tid(), To :: ets:tid()) -> 'ok'. + +ets_move(T1, T2) -> + F = fun(Es, A) -> true = ets:insert(T2, Es), A end, + [] = ets_take(ets:first(T1), T1, F, []), + ok. + +ets_take('$end_of_table', T, F, A) -> + case ets:first(T) of % no safe_fixtable()... + '$end_of_table' -> A; + Key -> ets_take(Key, T, F, A) + end; +ets_take(Key, T, F, A) -> + Vs = ets:lookup(T, Key), + Key1 = ets:next(T, Key), + true = ets:delete(T, Key), + ets_take(Key1, T, F, F(Vs, A)). + -spec parallelism() -> integer(). parallelism() -> - CPUs = erlang:system_info(logical_processors_available), - Schedulers = erlang:system_info(schedulers), - min(CPUs, Schedulers). + erlang:system_info(schedulers_online). -spec family([{K,V}]) -> [{K,[V]}]. diff --git a/lib/dialyzer/src/dialyzer_worker.erl b/lib/dialyzer/src/dialyzer_worker.erl index b9ab27c11d..af0f2e9e08 100644 --- a/lib/dialyzer/src/dialyzer_worker.erl +++ b/lib/dialyzer/src/dialyzer_worker.erl @@ -1,8 +1,4 @@ %% -*- erlang-indent-level: 2 -*- -%%----------------------------------------------------------------------- -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -15,9 +11,6 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% -%% %CopyrightEnd% -%% -module(dialyzer_worker). @@ -63,10 +56,14 @@ launch(Mode, Job, InitData, Coordinator) -> %%-------------------------------------------------------------------- -init(#state{job = SCC, mode = Mode, init_data = InitData} = State) when +init(#state{job = SCC, mode = Mode, init_data = InitData, + coordinator = Coordinator} = State) when Mode =:= 'typesig'; Mode =:= 'dataflow' -> - DependsOn = dialyzer_succ_typings:find_depends_on(SCC, InitData), - ?debug("Deps ~p: ~p\n",[SCC, DependsOn]), + DependsOnSCCs = dialyzer_succ_typings:find_depends_on(SCC, InitData), + ?debug("~w: Deps ~p: ~p\n", [self(), SCC, DependsOnSCCs]), + Pids = dialyzer_coordinator:sccs_to_pids(DependsOnSCCs, Coordinator), + ?debug("~w: PidsDeps ~p\n", [self(), Pids]), + DependsOn = [{Pid, erlang:monitor(process, Pid)} || Pid <- Pids], loop(updating, State#state{depends_on = DependsOn}); init(#state{mode = Mode} = State) when Mode =:= 'compile'; Mode =:= 'warnings' -> @@ -74,7 +71,7 @@ init(#state{mode = Mode} = State) when loop(updating, #state{mode = Mode} = State) when Mode =:= 'typesig'; Mode =:= 'dataflow' -> - ?debug("Update: ~p\n",[State#state.job]), + ?debug("~w: Update: ~p\n", [self(), State#state.job]), NextStatus = case waits_more_success_typings(State) of true -> waiting; @@ -83,11 +80,11 @@ loop(updating, #state{mode = Mode} = State) when loop(NextStatus, State); loop(waiting, #state{mode = Mode} = State) when Mode =:= 'typesig'; Mode =:= 'dataflow' -> - ?debug("Wait: ~p\n",[State#state.job]), + ?debug("~w: Wait: ~p\n", [self(), State#state.job]), NewState = wait_for_success_typings(State), loop(updating, NewState); loop(running, #state{mode = 'compile'} = State) -> - dialyzer_coordinator:request_activation(State#state.coordinator), + request_activation(State), ?debug("Compile: ~s\n",[State#state.job]), Result = case start_compilation(State) of @@ -99,51 +96,28 @@ loop(running, #state{mode = 'compile'} = State) -> end, report_to_coordinator(Result, State); loop(running, #state{mode = 'warnings'} = State) -> - dialyzer_coordinator:request_activation(State#state.coordinator), + request_activation(State), ?debug("Warning: ~s\n",[State#state.job]), Result = collect_warnings(State), report_to_coordinator(Result, State); loop(running, #state{mode = Mode} = State) when Mode =:= 'typesig'; Mode =:= 'dataflow' -> request_activation(State), - ?debug("Run: ~p\n",[State#state.job]), + ?debug("~w: Run: ~p\n", [self(), State#state.job]), NotFixpoint = do_work(State), - ok = broadcast_done(State), report_to_coordinator(NotFixpoint, State). waits_more_success_typings(#state{depends_on = Depends}) -> Depends =/= []. -broadcast_done(#state{job = SCC, init_data = InitData, - coordinator = Coordinator}) -> - RequiredBy = dialyzer_succ_typings:find_required_by(SCC, InitData), - {Callers, Unknown} = - dialyzer_coordinator:sccs_to_pids(RequiredBy, Coordinator), - send_done(Callers, SCC), - continue_broadcast_done(Unknown, SCC, Coordinator). - -send_done(Callers, SCC) -> - ?debug("Sending ~p: ~p\n",[SCC, Callers]), - SendSTFun = fun(PID) -> PID ! {done, SCC} end, - lists:foreach(SendSTFun, Callers). - -continue_broadcast_done([], _SCC, _Coordinator) -> ok; -continue_broadcast_done(Rest, SCC, Coordinator) -> - %% This time limit should be greater than the time required - %% by the coordinator to spawn all processes. - timer:sleep(500), - {Callers, Unknown} = dialyzer_coordinator:sccs_to_pids(Rest, Coordinator), - send_done(Callers, SCC), - continue_broadcast_done(Unknown, SCC, Coordinator). - wait_for_success_typings(#state{depends_on = DependsOn} = State) -> receive - {done, SCC} -> - ?debug("GOT ~p: ~p\n",[State#state.job, SCC]), - State#state{depends_on = DependsOn -- [SCC]} + {'DOWN', Ref, process, Pid, _Info} -> + ?debug("~w: ~p got DOWN: ~p\n", [self(), State#state.job, Pid]), + State#state{depends_on = DependsOn -- [{Pid, Ref}]} after 5000 -> - ?debug("Still Waiting ~p: ~p\n",[State#state.job, DependsOn]), + ?debug("~w: Still Waiting ~p:\n ~p\n", [self(), State#state.job, DependsOn]), State end. @@ -157,7 +131,7 @@ do_work(#state{mode = Mode, job = Job, init_data = InitData}) -> end. report_to_coordinator(Result, #state{job = Job, coordinator = Coordinator}) -> - ?debug("Done: ~p\n",[Job]), + ?debug("~w: Done: ~p\n",[self(), Job]), dialyzer_coordinator:job_done(Job, Result, Coordinator). start_compilation(#state{job = Job, init_data = InitData}) -> diff --git a/lib/dialyzer/src/typer.erl b/lib/dialyzer/src/typer.erl new file mode 100644 index 0000000000..16b9c8a94a --- /dev/null +++ b/lib/dialyzer/src/typer.erl @@ -0,0 +1,1107 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +%%----------------------------------------------------------------------- +%% File : typer.erl +%% Author(s) : The first version of typer was written by Bingwen He +%% with guidance from Kostis Sagonas and Tobias Lindahl. +%% Since June 2008 typer is maintained by Kostis Sagonas. +%% Description : An Erlang/OTP application that shows type information +%% for Erlang modules to the user. Additionally, it can +%% annotate the code of files with such type information. +%%----------------------------------------------------------------------- + +-module(typer). + +-export([start/0]). + +%%----------------------------------------------------------------------- + +-define(SHOW, show). +-define(SHOW_EXPORTED, show_exported). +-define(ANNOTATE, annotate). +-define(ANNOTATE_INC_FILES, annotate_inc_files). + +-type mode() :: ?SHOW | ?SHOW_EXPORTED | ?ANNOTATE | ?ANNOTATE_INC_FILES. + +%%----------------------------------------------------------------------- + +-type files() :: [file:filename()]. +-type callgraph() :: dialyzer_callgraph:callgraph(). +-type codeserver() :: dialyzer_codeserver:codeserver(). +-type plt() :: dialyzer_plt:plt(). + +-record(analysis, + {mode :: mode() | 'undefined', + macros = [] :: [{atom(), term()}], + includes = [] :: files(), + codeserver = dialyzer_codeserver:new():: codeserver(), + callgraph = dialyzer_callgraph:new() :: callgraph(), + files = [] :: files(), % absolute names + plt = none :: 'none' | file:filename(), + no_spec = false :: boolean(), + show_succ = false :: boolean(), + %% For choosing between specs or edoc @spec comments + edoc = false :: boolean(), + %% Files in 'fms' are compilable with option 'to_pp'; we keep them + %% as {FileName, ModuleName} in case the ModuleName is different + fms = [] :: [{file:filename(), module()}], + ex_func = map__new() :: map_dict(), + record = map__new() :: map_dict(), + func = map__new() :: map_dict(), + inc_func = map__new() :: map_dict(), + trust_plt = dialyzer_plt:new() :: plt()}). +-type analysis() :: #analysis{}. + +-record(args, {files = [] :: files(), + files_r = [] :: files(), + trusted = [] :: files()}). +-type args() :: #args{}. + +%%-------------------------------------------------------------------- + +-spec start() -> no_return(). + +start() -> + _ = io:setopts(standard_error, [{encoding,unicode}]), + _ = io:setopts([{encoding,unicode}]), + {Args, Analysis} = process_cl_args(), + %% io:format("Args: ~p\n", [Args]), + %% io:format("Analysis: ~p\n", [Analysis]), + Timer = dialyzer_timing:init(false), + TrustedFiles = filter_fd(Args#args.trusted, [], fun is_erl_file/1), + Analysis2 = extract(Analysis, TrustedFiles), + All_Files = get_all_files(Args), + %% io:format("All_Files: ~tp\n", [All_Files]), + Analysis3 = Analysis2#analysis{files = All_Files}, + Analysis4 = collect_info(Analysis3), + %% io:format("Final: ~p\n", [Analysis4#analysis.fms]), + TypeInfo = get_type_info(Analysis4), + dialyzer_timing:stop(Timer), + show_or_annotate(TypeInfo), + %% io:format("\nTyper analysis finished\n"), + erlang:halt(0). + +%%-------------------------------------------------------------------- + +-spec extract(analysis(), files()) -> analysis(). + +extract(#analysis{macros = Macros, + includes = Includes, + trust_plt = TrustPLT} = Analysis, TrustedFiles) -> + %% io:format("--- Extracting trusted typer_info... "), + Ds = [{d, Name, Value} || {Name, Value} <- Macros], + CodeServer = dialyzer_codeserver:new(), + Fun = + fun(File, CS) -> + %% We include one more dir; the one above the one we are trusting + %% E.g, for /home/tests/typer_ann/test.ann.erl, we should include + %% /home/tests/ rather than /home/tests/typer_ann/ + AllIncludes = [filename:dirname(filename:dirname(File)) | Includes], + Is = [{i, Dir} || Dir <- AllIncludes], + CompOpts = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds, + case dialyzer_utils:get_core_from_src(File, CompOpts) of + {ok, Core} -> + case dialyzer_utils:get_record_and_type_info(Core) of + {ok, RecDict} -> + Mod = list_to_atom(filename:basename(File, ".erl")), + case dialyzer_utils:get_spec_info(Mod, Core, RecDict) of + {ok, SpecDict, CbDict} -> + CS1 = dialyzer_codeserver:store_temp_records(Mod, RecDict, CS), + dialyzer_codeserver:store_temp_contracts(Mod, SpecDict, CbDict, CS1); + {error, Reason} -> compile_error([Reason]) + end; + {error, Reason} -> compile_error([Reason]) + end; + {error, Reason} -> compile_error(Reason) + end + end, + CodeServer1 = lists:foldl(Fun, CodeServer, TrustedFiles), + %% Process remote types + NewCodeServer = + try + CodeServer2 = + dialyzer_utils:merge_types(CodeServer1, + TrustPLT), % XXX change to the PLT? + NewExpTypes = dialyzer_codeserver:get_temp_exported_types(CodeServer1), + case sets:size(NewExpTypes) of 0 -> ok end, + CodeServer3 = dialyzer_codeserver:finalize_exported_types(NewExpTypes, CodeServer2), + CodeServer4 = dialyzer_utils:process_record_remote_types(CodeServer3), + dialyzer_contracts:process_contract_remote_types(CodeServer4) + catch + throw:{error, ErrorMsg} -> + compile_error(ErrorMsg) + end, + %% Create TrustPLT + ContractsDict = dialyzer_codeserver:get_contracts(NewCodeServer), + Contracts = orddict:from_list(dict:to_list(ContractsDict)), + NewTrustPLT = dialyzer_plt:insert_contract_list(TrustPLT, Contracts), + Analysis#analysis{trust_plt = NewTrustPLT}. + +%%-------------------------------------------------------------------- + +-spec get_type_info(analysis()) -> analysis(). + +get_type_info(#analysis{callgraph = CallGraph, + trust_plt = TrustPLT, + codeserver = CodeServer} = Analysis) -> + StrippedCallGraph = remove_external(CallGraph, TrustPLT), + %% io:format("--- Analyzing callgraph... "), + try + NewPlt = dialyzer_succ_typings:analyze_callgraph(StrippedCallGraph, + TrustPLT, + CodeServer), + Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt} + catch + error:What -> + fatal_error(io_lib:format("Analysis failed with message: ~tp", + [{What, erlang:get_stacktrace()}])); + throw:{dialyzer_succ_typing_error, Msg} -> + fatal_error(io_lib:format("Analysis failed with message: ~ts", [Msg])) + end. + +-spec remove_external(callgraph(), plt()) -> callgraph(). + +remove_external(CallGraph, PLT) -> + {StrippedCG0, Ext} = dialyzer_callgraph:remove_external(CallGraph), + case get_external(Ext, PLT) of + [] -> ok; + Externals -> + msg(io_lib:format(" Unknown functions: ~tp\n", [lists:usort(Externals)])), + ExtTypes = rcv_ext_types(), + case ExtTypes of + [] -> ok; + _ -> msg(io_lib:format(" Unknown types: ~tp\n", [ExtTypes])) + end + end, + StrippedCG0. + +-spec get_external([{mfa(), mfa()}], plt()) -> [mfa()]. + +get_external(Exts, Plt) -> + Fun = fun ({_From, To = {M, F, A}}, Acc) -> + case dialyzer_plt:contains_mfa(Plt, To) of + false -> + case erl_bif_types:is_known(M, F, A) of + true -> Acc; + false -> [To|Acc] + end; + true -> Acc + end + end, + lists:foldl(Fun, [], Exts). + +%%-------------------------------------------------------------------- +%% Showing type information or annotating files with such information. +%%-------------------------------------------------------------------- + +-define(TYPER_ANN_DIR, "typer_ann"). + +-type line() :: non_neg_integer(). +-type fa() :: {atom(), arity()}. +-type func_info() :: {line(), atom(), arity()}. + +-record(info, {records = maps:new() :: erl_types:type_table(), + functions = [] :: [func_info()], + types = map__new() :: map_dict(), + edoc = false :: boolean()}). +-record(inc, {map = map__new() :: map_dict(), filter = [] :: files()}). +-type inc() :: #inc{}. + +-spec show_or_annotate(analysis()) -> 'ok'. + +show_or_annotate(#analysis{mode = Mode, fms = Files} = Analysis) -> + case Mode of + ?SHOW -> show(Analysis); + ?SHOW_EXPORTED -> show(Analysis); + ?ANNOTATE -> + Fun = fun ({File, Module}) -> + Info = get_final_info(File, Module, Analysis), + write_typed_file(File, Info) + end, + lists:foreach(Fun, Files); + ?ANNOTATE_INC_FILES -> + IncInfo = write_and_collect_inc_info(Analysis), + write_inc_files(IncInfo) + end. + +write_and_collect_inc_info(Analysis) -> + Fun = fun ({File, Module}, Inc) -> + Info = get_final_info(File, Module, Analysis), + write_typed_file(File, Info), + IncFuns = get_functions(File, Analysis), + collect_imported_functions(IncFuns, Info#info.types, Inc) + end, + NewInc = lists:foldl(Fun, #inc{}, Analysis#analysis.fms), + clean_inc(NewInc). + +write_inc_files(Inc) -> + Fun = + fun (File) -> + Val = map__lookup(File, Inc#inc.map), + %% Val is function with its type info + %% in form [{{Line,F,A},Type}] + Functions = [Key || {Key, _} <- Val], + Val1 = [{{F,A},Type} || {{_Line,F,A},Type} <- Val], + Info = #info{types = map__from_list(Val1), + records = maps:new(), + %% Note we need to sort functions here! + functions = lists:keysort(1, Functions)}, + %% io:format("Types ~tp\n", [Info#info.types]), + %% io:format("Functions ~tp\n", [Info#info.functions]), + %% io:format("Records ~tp\n", [Info#info.records]), + write_typed_file(File, Info) + end, + lists:foreach(Fun, dict:fetch_keys(Inc#inc.map)). + +show(Analysis) -> + Fun = fun ({File, Module}) -> + Info = get_final_info(File, Module, Analysis), + show_type_info(File, Info) + end, + lists:foreach(Fun, Analysis#analysis.fms). + +get_final_info(File, Module, Analysis) -> + Records = get_records(File, Analysis), + Types = get_types(Module, Analysis, Records), + Functions = get_functions(File, Analysis), + Edoc = Analysis#analysis.edoc, + #info{records = Records, functions = Functions, types = Types, edoc = Edoc}. + +collect_imported_functions(Functions, Types, Inc) -> + %% Coming from other sourses, including: + %% FIXME: How to deal with yecc-generated file???? + %% --.yrl (yecc-generated file)??? + %% -- yeccpre.hrl (yecc-generated file)??? + %% -- other cases + Fun = fun ({File, _} = Obj, I) -> + case is_yecc_gen(File, I) of + {true, NewI} -> NewI; + {false, NewI} -> + check_imported_functions(Obj, NewI, Types) + end + end, + lists:foldl(Fun, Inc, Functions). + +-spec is_yecc_gen(file:filename(), inc()) -> {boolean(), inc()}. + +is_yecc_gen(File, #inc{filter = Fs} = Inc) -> + case lists:member(File, Fs) of + true -> {true, Inc}; + false -> + case filename:extension(File) of + ".yrl" -> + Rootname = filename:rootname(File, ".yrl"), + Obj = Rootname ++ ".erl", + case lists:member(Obj, Fs) of + true -> {true, Inc}; + false -> + NewInc = Inc#inc{filter = [Obj|Fs]}, + {true, NewInc} + end; + _ -> + case filename:basename(File) of + "yeccpre.hrl" -> {true, Inc}; + _ -> {false, Inc} + end + end + end. + +check_imported_functions({File, {Line, F, A}}, Inc, Types) -> + IncMap = Inc#inc.map, + FA = {F, A}, + Type = get_type_info(FA, Types), + case map__lookup(File, IncMap) of + none -> %% File is not added. Add it + Obj = {File,[{FA, {Line, Type}}]}, + NewMap = map__insert(Obj, IncMap), + Inc#inc{map = NewMap}; + Val -> %% File is already in. Check. + case lists:keyfind(FA, 1, Val) of + false -> + %% Function is not in; add it + Obj = {File, Val ++ [{FA, {Line, Type}}]}, + NewMap = map__insert(Obj, IncMap), + Inc#inc{map = NewMap}; + Type -> + %% Function is in and with same type + Inc; + _ -> + %% Function is in but with diff type + inc_warning(FA, File), + Elem = lists:keydelete(FA, 1, Val), + NewMap = case Elem of + [] -> map__remove(File, IncMap); + _ -> map__insert({File, Elem}, IncMap) + end, + Inc#inc{map = NewMap} + end + end. + +inc_warning({F, A}, File) -> + io:format(" ***Warning: Skip function ~tp/~p ", [F, A]), + io:format("in file ~tp because of inconsistent type\n", [File]). + +clean_inc(Inc) -> + Inc1 = remove_yecc_generated_file(Inc), + normalize_obj(Inc1). + +remove_yecc_generated_file(#inc{filter = Filter} = Inc) -> + Fun = fun (Key, #inc{map = Map} = I) -> + I#inc{map = map__remove(Key, Map)} + end, + lists:foldl(Fun, Inc, Filter). + +normalize_obj(TmpInc) -> + Fun = fun (Key, Val, Inc) -> + NewVal = [{{Line,F,A},Type} || {{F,A},{Line,Type}} <- Val], + map__insert({Key, NewVal}, Inc) + end, + TmpInc#inc{map = map__fold(Fun, map__new(), TmpInc#inc.map)}. + +get_records(File, Analysis) -> + map__lookup(File, Analysis#analysis.record). + +get_types(Module, Analysis, Records) -> + TypeInfoPlt = Analysis#analysis.trust_plt, + TypeInfo = + case dialyzer_plt:lookup_module(TypeInfoPlt, Module) of + none -> []; + {value, List} -> List + end, + CodeServer = Analysis#analysis.codeserver, + TypeInfoList = + case Analysis#analysis.show_succ of + true -> + [convert_type_info(I) || I <- TypeInfo]; + false -> + [get_type(I, CodeServer, Records) || I <- TypeInfo] + end, + map__from_list(TypeInfoList). + +convert_type_info({{_M, F, A}, Range, Arg}) -> + {{F, A}, {Range, Arg}}. + +get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records) -> + case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of + error -> + {{F, A}, {Range, Arg}}; + {ok, {_FileLine, Contract, _Xtra}} -> + Sig = erl_types:t_fun(Arg, Range), + case dialyzer_contracts:check_contract(Contract, Sig) of + ok -> {{F, A}, {contract, Contract}}; + {error, {extra_range, _, _}} -> + {{F, A}, {contract, Contract}}; + {error, {overlapping_contract, []}} -> + {{F, A}, {contract, Contract}}; + {error, invalid_contract} -> + CString = dialyzer_contracts:contract_to_string(Contract), + SigString = dialyzer_utils:format_sig(Sig, Records), + Msg = io_lib:format("Error in contract of function ~w:~tw/~w\n" + "\t The contract is: " ++ CString ++ "\n" ++ + "\t but the inferred signature is: ~ts", + [M, F, A, SigString]), + fatal_error(Msg); + {error, ErrorStr} when is_list(ErrorStr) -> % ErrorStr is a string() + Msg = io_lib:format("Error in contract of function ~w:~tw/~w: ~ts", + [M, F, A, ErrorStr]), + fatal_error(Msg) + end + end. + +get_functions(File, Analysis) -> + case Analysis#analysis.mode of + ?SHOW -> + Funcs = map__lookup(File, Analysis#analysis.func), + Inc_Funcs = map__lookup(File, Analysis#analysis.inc_func), + remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs); + ?SHOW_EXPORTED -> + Ex_Funcs = map__lookup(File, Analysis#analysis.ex_func), + remove_module_info(Ex_Funcs); + ?ANNOTATE -> + Funcs = map__lookup(File, Analysis#analysis.func), + remove_module_info(Funcs); + ?ANNOTATE_INC_FILES -> + map__lookup(File, Analysis#analysis.inc_func) + end. + +normalize_incFuncs(Functions) -> + [FunInfo || {_FileName, FunInfo} <- Functions]. + +-spec remove_module_info([func_info()]) -> [func_info()]. + +remove_module_info(FunInfoList) -> + F = fun ({_,module_info,0}) -> false; + ({_,module_info,1}) -> false; + ({Line,F,A}) when is_integer(Line), is_atom(F), is_integer(A) -> true + end, + lists:filter(F, FunInfoList). + +write_typed_file(File, Info) -> + io:format(" Processing file: ~tp\n", [File]), + Dir = filename:dirname(File), + RootName = filename:basename(filename:rootname(File)), + Ext = filename:extension(File), + TyperAnnDir = filename:join(Dir, ?TYPER_ANN_DIR), + TmpNewFilename = lists:concat([RootName, ".ann", Ext]), + NewFileName = filename:join(TyperAnnDir, TmpNewFilename), + case file:make_dir(TyperAnnDir) of + {error, Reason} -> + case Reason of + eexist -> %% TypEr dir exists; remove old typer files if they exist + case file:delete(NewFileName) of + ok -> ok; + {error, enoent} -> ok; + {error, _} -> + Msg = io_lib:format("Error in deleting file ~ts\n", [NewFileName]), + fatal_error(Msg) + end, + write_typed_file(File, Info, NewFileName); + enospc -> + Msg = io_lib:format("Not enough space in ~tp\n", [Dir]), + fatal_error(Msg); + eacces -> + Msg = io_lib:format("No write permission in ~tp\n", [Dir]), + fatal_error(Msg); + _ -> + Msg = io_lib:format("Unhandled error ~ts when writing ~tp\n", + [Reason, Dir]), + fatal_error(Msg) + end; + ok -> %% Typer dir does NOT exist + write_typed_file(File, Info, NewFileName) + end. + +write_typed_file(File, Info, NewFileName) -> + {ok, Binary} = file:read_file(File), + Chars = unicode:characters_to_list(Binary), + write_typed_file(Chars, NewFileName, Info, 1, []), + io:format(" Saved as: ~tp\n", [NewFileName]). + +write_typed_file(Chars, File, #info{functions = []}, _LNo, _Acc) -> + ok = file:write_file(File, unicode:characters_to_binary(Chars), [append]); +write_typed_file([Ch|Chs] = Chars, File, Info, LineNo, Acc) -> + [{Line,F,A}|RestFuncs] = Info#info.functions, + case Line of + 1 -> %% This will happen only for inc files + ok = raw_write(F, A, Info, File, []), + NewInfo = Info#info{functions = RestFuncs}, + NewAcc = [], + write_typed_file(Chars, File, NewInfo, Line, NewAcc); + _ -> + case Ch of + 10 -> + NewLineNo = LineNo + 1, + {NewInfo, NewAcc} = + case NewLineNo of + Line -> + ok = raw_write(F, A, Info, File, [Ch|Acc]), + {Info#info{functions = RestFuncs}, []}; + _ -> + {Info, [Ch|Acc]} + end, + write_typed_file(Chs, File, NewInfo, NewLineNo, NewAcc); + _ -> + write_typed_file(Chs, File, Info, LineNo, [Ch|Acc]) + end + end. + +raw_write(F, A, Info, File, Content) -> + TypeInfo = get_type_string(F, A, Info, file), + ContentList = lists:reverse(Content) ++ TypeInfo ++ "\n", + ContentBin = unicode:characters_to_binary(ContentList), + file:write_file(File, ContentBin, [append]). + +get_type_string(F, A, Info, Mode) -> + Type = get_type_info({F,A}, Info#info.types), + TypeStr = + case Type of + {contract, C} -> + dialyzer_contracts:contract_to_string(C); + {RetType, ArgType} -> + Sig = erl_types:t_fun(ArgType, RetType), + dialyzer_utils:format_sig(Sig, Info#info.records) + end, + case Info#info.edoc of + false -> + case {Mode, Type} of + {file, {contract, _}} -> ""; + _ -> + Prefix = lists:concat(["-spec ", erl_types:atom_to_string(F)]), + lists:concat([Prefix, TypeStr, "."]) + end; + true -> + Prefix = lists:concat(["%% @spec ", F]), + lists:concat([Prefix, TypeStr, "."]) + end. + +show_type_info(File, Info) -> + io:format("\n%% File: ~tp\n%% ", [File]), + OutputString = lists:concat(["~.", length(File)+8, "c~n"]), + io:fwrite(OutputString, [$-]), + Fun = fun ({_LineNo, F, A}) -> + TypeInfo = get_type_string(F, A, Info, show), + io:format("~ts\n", [TypeInfo]) + end, + lists:foreach(Fun, Info#info.functions). + +get_type_info(Func, Types) -> + case map__lookup(Func, Types) of + none -> + %% Note: Typeinfo of any function should exist in + %% the result offered by dialyzer, otherwise there + %% *must* be something wrong with the analysis + Msg = io_lib:format("No type info for function: ~tp\n", [Func]), + fatal_error(Msg); + {contract, _Fun} = C -> C; + {_RetType, _ArgType} = RA -> RA + end. + +%%-------------------------------------------------------------------- +%% Processing of command-line options and arguments. +%%-------------------------------------------------------------------- + +-spec process_cl_args() -> {args(), analysis()}. + +process_cl_args() -> + ArgList = init:get_plain_arguments(), + %% io:format("Args is ~tp\n", [ArgList]), + {Args, Analysis} = analyze_args(ArgList, #args{}, #analysis{}), + %% if the mode has not been set, set it to the default mode (show) + {Args, case Analysis#analysis.mode of + undefined -> Analysis#analysis{mode = ?SHOW}; + Mode when is_atom(Mode) -> Analysis + end}. + +analyze_args([], Args, Analysis) -> + {Args, Analysis}; +analyze_args(ArgList, Args, Analysis) -> + {Result, Rest} = cl(ArgList), + {NewArgs, NewAnalysis} = analyze_result(Result, Args, Analysis), + analyze_args(Rest, NewArgs, NewAnalysis). + +cl(["-h"|_]) -> help_message(); +cl(["--help"|_]) -> help_message(); +cl(["-v"|_]) -> version_message(); +cl(["--version"|_]) -> version_message(); +cl(["--edoc"|Opts]) -> {edoc, Opts}; +cl(["--show"|Opts]) -> {{mode, ?SHOW}, Opts}; +cl(["--show_exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts}; +cl(["--show-exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts}; +cl(["--show_success_typings"|Opts]) -> {show_succ, Opts}; +cl(["--show-success-typings"|Opts]) -> {show_succ, Opts}; +cl(["--annotate"|Opts]) -> {{mode, ?ANNOTATE}, Opts}; +cl(["--annotate-inc-files"|Opts]) -> {{mode, ?ANNOTATE_INC_FILES}, Opts}; +cl(["--no_spec"|Opts]) -> {no_spec, Opts}; +cl(["--plt",Plt|Opts]) -> {{plt, Plt}, Opts}; +cl(["-D"++Def|Opts]) -> + case Def of + "" -> fatal_error("no variable name specified after -D"); + _ -> + DefPair = process_def_list(re:split(Def, "=", [{return, list}, unicode])), + {{def, DefPair}, Opts} + end; +cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts}; +cl(["-I"++Dir|Opts]) -> + case Dir of + "" -> fatal_error("no include directory specified after -I"); + _ -> {{inc, Dir}, Opts} + end; +cl(["-T"|Opts]) -> + {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + case Files of + [] -> fatal_error("no file or directory specified after -T"); + [_|_] -> {{trusted, Files}, RestOpts} + end; +cl(["-r"|Opts]) -> + {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {{files_r, Files}, RestOpts}; +cl(["-pa",Dir|Opts]) -> {{pa,Dir}, Opts}; +cl(["-pz",Dir|Opts]) -> {{pz,Dir}, Opts}; +cl(["-"++H|_]) -> fatal_error("unknown option -"++H); +cl(Opts) -> + {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {{files, Files}, RestOpts}. + +process_def_list(L) -> + case L of + [Name, Value] -> + {ok, Tokens, _} = erl_scan:string(Value ++ "."), + {ok, ErlValue} = erl_parse:parse_term(Tokens), + {list_to_atom(Name), ErlValue}; + [Name] -> + {list_to_atom(Name), true} + end. + +%% Get information about files that the user trusts and wants to analyze +analyze_result({files, Val}, Args, Analysis) -> + NewVal = Args#args.files ++ Val, + {Args#args{files = NewVal}, Analysis}; +analyze_result({files_r, Val}, Args, Analysis) -> + NewVal = Args#args.files_r ++ Val, + {Args#args{files_r = NewVal}, Analysis}; +analyze_result({trusted, Val}, Args, Analysis) -> + NewVal = Args#args.trusted ++ Val, + {Args#args{trusted = NewVal}, Analysis}; +analyze_result(edoc, Args, Analysis) -> + {Args, Analysis#analysis{edoc = true}}; +%% Get useful information for actual analysis +analyze_result({mode, Mode}, Args, Analysis) -> + case Analysis#analysis.mode of + undefined -> {Args, Analysis#analysis{mode = Mode}}; + OldMode -> mode_error(OldMode, Mode) + end; +analyze_result({def, Val}, Args, Analysis) -> + NewVal = Analysis#analysis.macros ++ [Val], + {Args, Analysis#analysis{macros = NewVal}}; +analyze_result({inc, Val}, Args, Analysis) -> + NewVal = Analysis#analysis.includes ++ [Val], + {Args, Analysis#analysis{includes = NewVal}}; +analyze_result({plt, Plt}, Args, Analysis) -> + {Args, Analysis#analysis{plt = Plt}}; +analyze_result(show_succ, Args, Analysis) -> + {Args, Analysis#analysis{show_succ = true}}; +analyze_result(no_spec, Args, Analysis) -> + {Args, Analysis#analysis{no_spec = true}}; +analyze_result({pa, Dir}, Args, Analysis) -> + true = code:add_patha(Dir), + {Args, Analysis}; +analyze_result({pz, Dir}, Args, Analysis) -> + true = code:add_pathz(Dir), + {Args, Analysis}. + +%%-------------------------------------------------------------------- +%% File processing. +%%-------------------------------------------------------------------- + +-spec get_all_files(args()) -> [file:filename(),...]. + +get_all_files(#args{files = Fs, files_r = Ds}) -> + case filter_fd(Fs, Ds, fun test_erl_file_exclude_ann/1) of + [] -> fatal_error("no file(s) to analyze"); + AllFiles -> AllFiles + end. + +-spec test_erl_file_exclude_ann(file:filename()) -> boolean(). + +test_erl_file_exclude_ann(File) -> + case is_erl_file(File) of + true -> %% Exclude files ending with ".ann.erl" + case re:run(File, "[\.]ann[\.]erl$", [unicode]) of + {match, _} -> false; + nomatch -> true + end; + false -> false + end. + +-spec is_erl_file(file:filename()) -> boolean(). + +is_erl_file(File) -> + filename:extension(File) =:= ".erl". + +-type test_file_fun() :: fun((file:filename()) -> boolean()). + +-spec filter_fd(files(), files(), test_file_fun()) -> files(). + +filter_fd(File_Dir, Dir_R, Fun) -> + All_File_1 = process_file_and_dir(File_Dir, Fun), + All_File_2 = process_dir_rec(Dir_R, Fun), + remove_dup(All_File_1 ++ All_File_2). + +-spec process_file_and_dir(files(), test_file_fun()) -> files(). + +process_file_and_dir(File_Dir, TestFun) -> + Fun = + fun (Elem, Acc) -> + case filelib:is_regular(Elem) of + true -> process_file(Elem, TestFun, Acc); + false -> check_dir(Elem, false, Acc, TestFun) + end + end, + lists:foldl(Fun, [], File_Dir). + +-spec process_dir_rec(files(), test_file_fun()) -> files(). + +process_dir_rec(Dirs, TestFun) -> + Fun = fun (Dir, Acc) -> check_dir(Dir, true, Acc, TestFun) end, + lists:foldl(Fun, [], Dirs). + +-spec check_dir(file:filename(), boolean(), files(), test_file_fun()) -> files(). + +check_dir(Dir, Recursive, Acc, Fun) -> + case file:list_dir(Dir) of + {ok, Files} -> + {TmpDirs, TmpFiles} = split_dirs_and_files(Files, Dir), + case Recursive of + false -> + FinalFiles = process_file_and_dir(TmpFiles, Fun), + Acc ++ FinalFiles; + true -> + TmpAcc1 = process_file_and_dir(TmpFiles, Fun), + TmpAcc2 = process_dir_rec(TmpDirs, Fun), + Acc ++ TmpAcc1 ++ TmpAcc2 + end; + {error, eacces} -> + fatal_error("no access permission to dir \""++Dir++"\""); + {error, enoent} -> + fatal_error("cannot access "++Dir++": No such file or directory"); + {error, _Reason} -> + fatal_error("error involving a use of file:list_dir/1") + end. + +%% Same order as the input list +-spec process_file(file:filename(), test_file_fun(), files()) -> files(). + +process_file(File, TestFun, Acc) -> + case TestFun(File) of + true -> Acc ++ [File]; + false -> Acc + end. + +%% Same order as the input list +-spec split_dirs_and_files(files(), file:filename()) -> {files(), files()}. + +split_dirs_and_files(Elems, Dir) -> + Test_Fun = + fun (Elem, {DirAcc, FileAcc}) -> + File = filename:join(Dir, Elem), + case filelib:is_regular(File) of + false -> {[File|DirAcc], FileAcc}; + true -> {DirAcc, [File|FileAcc]} + end + end, + {Dirs, Files} = lists:foldl(Test_Fun, {[], []}, Elems), + {lists:reverse(Dirs), lists:reverse(Files)}. + +%% Removes duplicate filenames but keeps the order of the input list +-spec remove_dup(files()) -> files(). + +remove_dup(Files) -> + Test_Dup = fun (File, Acc) -> + case lists:member(File, Acc) of + true -> Acc; + false -> [File|Acc] + end + end, + Reversed_Elems = lists:foldl(Test_Dup, [], Files), + lists:reverse(Reversed_Elems). + +%%-------------------------------------------------------------------- +%% Collect information. +%%-------------------------------------------------------------------- + +-type inc_file_info() :: {file:filename(), func_info()}. + +-record(tmpAcc, {file :: file:filename(), + module :: atom(), + funcAcc = [] :: [func_info()], + incFuncAcc = [] :: [inc_file_info()], + dialyzerObj = [] :: [{mfa(), {_, _}}]}). + +-spec collect_info(analysis()) -> analysis(). + +collect_info(Analysis) -> + NewPlt = + try get_dialyzer_plt(Analysis) of + DialyzerPlt -> + dialyzer_plt:merge_plts([Analysis#analysis.trust_plt, DialyzerPlt]) + catch + throw:{dialyzer_error,_Reason} -> + fatal_error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it") + end, + NewAnalysis = lists:foldl(fun collect_one_file_info/2, + Analysis#analysis{trust_plt = NewPlt}, + Analysis#analysis.files), + %% Process Remote Types + TmpCServer = NewAnalysis#analysis.codeserver, + NewCServer = + try + TmpCServer1 = dialyzer_utils:merge_types(TmpCServer, NewPlt), + NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer), + OldExpTypes = dialyzer_plt:get_exported_types(NewPlt), + MergedExpTypes = sets:union(NewExpTypes, OldExpTypes), + TmpCServer2 = + dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1), + TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2), + dialyzer_contracts:process_contract_remote_types(TmpCServer3) + catch + throw:{error, ErrorMsg} -> + fatal_error(ErrorMsg) + end, + NewAnalysis#analysis{codeserver = NewCServer}. + +collect_one_file_info(File, Analysis) -> + Ds = [{d,Name,Val} || {Name,Val} <- Analysis#analysis.macros], + %% Current directory should also be included in "Includes". + Includes = [filename:dirname(File)|Analysis#analysis.includes], + Is = [{i,Dir} || Dir <- Includes], + Options = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds, + case dialyzer_utils:get_core_from_src(File, Options) of + {error, Reason} -> + %% io:format("File=~tp\n,Options=~p\n,Error=~p\n", [File,Options,Reason]), + compile_error(Reason); + {ok, Core} -> + case dialyzer_utils:get_record_and_type_info(Core) of + {error, Reason} -> compile_error([Reason]); + {ok, Records} -> + Mod = cerl:concrete(cerl:module_name(Core)), + case dialyzer_utils:get_spec_info(Mod, Core, Records) of + {error, Reason} -> compile_error([Reason]); + {ok, SpecInfo, CbInfo} -> + ExpTypes = get_exported_types_from_core(Core), + analyze_core_tree(Core, Records, SpecInfo, CbInfo, + ExpTypes, Analysis, File) + end + end + end. + +analyze_core_tree(Core, Records, SpecInfo, CbInfo, ExpTypes, Analysis, File) -> + Module = cerl:concrete(cerl:module_name(Core)), + TmpTree = cerl:from_records(Core), + CS1 = Analysis#analysis.codeserver, + NextLabel = dialyzer_codeserver:get_next_core_label(CS1), + {Tree, NewLabel} = cerl_trees:label(TmpTree, NextLabel), + CS2 = dialyzer_codeserver:insert(Module, Tree, CS1), + CS3 = dialyzer_codeserver:set_next_core_label(NewLabel, CS2), + CS4 = dialyzer_codeserver:store_temp_records(Module, Records, CS3), + CS5 = + case Analysis#analysis.no_spec of + true -> CS4; + false -> + dialyzer_codeserver:store_temp_contracts(Module, SpecInfo, CbInfo, CS4) + end, + OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CS5), + MergedExpTypes = sets:union(ExpTypes, OldExpTypes), + CS6 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, CS5), + Ex_Funcs = [{0,F,A} || {_,_,{F,A}} <- cerl:module_exports(Tree)], + CG = Analysis#analysis.callgraph, + {V, E} = dialyzer_callgraph:scan_core_tree(Tree, CG), + dialyzer_callgraph:add_edges(E, V, CG), + Fun = fun analyze_one_function/2, + All_Defs = cerl:module_defs(Tree), + Acc = lists:foldl(Fun, #tmpAcc{file = File, module = Module}, All_Defs), + Exported_FuncMap = map__insert({File, Ex_Funcs}, Analysis#analysis.ex_func), + %% we must sort all functions in the file which + %% originate from this file by *numerical order* of lineNo + Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc), + FuncMap = map__insert({File, Sorted_Functions}, Analysis#analysis.func), + %% we do not need to sort functions which are imported from included files + IncFuncMap = map__insert({File, Acc#tmpAcc.incFuncAcc}, + Analysis#analysis.inc_func), + FMs = Analysis#analysis.fms ++ [{File, Module}], + RecordMap = map__insert({File, Records}, Analysis#analysis.record), + Analysis#analysis{fms = FMs, + callgraph = CG, + codeserver = CS6, + ex_func = Exported_FuncMap, + inc_func = IncFuncMap, + record = RecordMap, + func = FuncMap}. + +analyze_one_function({Var, FunBody} = Function, Acc) -> + F = cerl:fname_id(Var), + A = cerl:fname_arity(Var), + TmpDialyzerObj = {{Acc#tmpAcc.module, F, A}, Function}, + NewDialyzerObj = Acc#tmpAcc.dialyzerObj ++ [TmpDialyzerObj], + Anno = cerl:get_ann(FunBody), + LineNo = get_line(Anno), + FileName = get_file(Anno), + BaseName = filename:basename(FileName), + FuncInfo = {LineNo, F, A}, + OriginalName = Acc#tmpAcc.file, + {FuncAcc, IncFuncAcc} = + case (FileName =:= OriginalName) orelse (BaseName =:= OriginalName) of + true -> %% Coming from original file + %% io:format("Added function ~tp\n", [{LineNo, F, A}]), + {Acc#tmpAcc.funcAcc ++ [FuncInfo], Acc#tmpAcc.incFuncAcc}; + false -> + %% Coming from other sourses, including: + %% -- .yrl (yecc-generated file) + %% -- yeccpre.hrl (yecc-generated file) + %% -- other cases + {Acc#tmpAcc.funcAcc, Acc#tmpAcc.incFuncAcc ++ [{FileName, FuncInfo}]} + end, + Acc#tmpAcc{funcAcc = FuncAcc, + incFuncAcc = IncFuncAcc, + dialyzerObj = NewDialyzerObj}. + +get_line([Line|_]) when is_integer(Line) -> Line; +get_line([_|T]) -> get_line(T); +get_line([]) -> none. + +get_file([{file,File}|_]) -> File; +get_file([_|T]) -> get_file(T); +get_file([]) -> "no_file". % should not happen + +-spec get_dialyzer_plt(analysis()) -> plt(). + +get_dialyzer_plt(#analysis{plt = PltFile0}) -> + PltFile = + case PltFile0 =:= none of + true -> dialyzer_plt:get_default_plt(); + false -> PltFile0 + end, + dialyzer_plt:from_file(PltFile). + +%% Exported Types + +get_exported_types_from_core(Core) -> + Attrs = cerl:module_attrs(Core), + ExpTypes1 = [cerl:concrete(L2) || {L1, L2} <- Attrs, + cerl:is_literal(L1), + cerl:is_literal(L2), + cerl:concrete(L1) =:= 'export_type'], + ExpTypes2 = lists:flatten(ExpTypes1), + M = cerl:atom_val(cerl:module_name(Core)), + sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]). + +%%-------------------------------------------------------------------- +%% Utilities for error reporting. +%%-------------------------------------------------------------------- + +-spec fatal_error(string()) -> no_return(). + +fatal_error(Slogan) -> + msg(io_lib:format("typer: ~ts\n", [Slogan])), + erlang:halt(1). + +-spec mode_error(mode(), mode()) -> no_return(). + +mode_error(OldMode, NewMode) -> + Msg = io_lib:format("Mode was previously set to '~s'; " + "can not set it to '~s' now", + [OldMode, NewMode]), + fatal_error(Msg). + +-spec compile_error([string()]) -> no_return(). + +compile_error(Reason) -> + JoinedString = lists:flatten([X ++ "\n" || X <- Reason]), + Msg = "Analysis failed with error report:\n" ++ JoinedString, + fatal_error(Msg). + +-spec msg(string()) -> 'ok'. + +msg(Msg) -> + io:format(standard_error, "~ts", [Msg]). + +%%-------------------------------------------------------------------- +%% Version and help messages. +%%-------------------------------------------------------------------- + +-spec version_message() -> no_return(). + +version_message() -> + io:format("TypEr version "++?VSN++"\n"), + erlang:halt(0). + +-spec help_message() -> no_return(). + +help_message() -> + S = <<" Usage: typer [--help] [--version] [--plt PLT] [--edoc] + [--show | --show-exported | --annotate | --annotate-inc-files] + [-Ddefine]* [-I include_dir]* [-pa dir]* [-pz dir]* + [-T application]* [-r] file* + + Options: + -r dir* + search directories recursively for .erl files below them + --show + Prints type specifications for all functions on stdout. + (this is the default behaviour; this option is not really needed) + --show-exported (or --show_exported) + Same as --show, but prints specifications for exported functions only + Specs are displayed sorted alphabetically on the function's name + --annotate + Annotates the specified files with type specifications + --annotate-inc-files + Same as --annotate but annotates all -include() files as well as + all .erl files (use this option with caution - has not been tested much) + --edoc + Prints type information as Edoc @spec comments, not as type specs + --plt PLT + Use the specified dialyzer PLT file rather than the default one + -T file* + The specified file(s) already contain type specifications and these + are to be trusted in order to print specs for the rest of the files + (Multiple files or dirs, separated by spaces, can be specified.) + -Dname (or -Dname=value) + pass the defined name(s) to TypEr + (The syntax of defines is the same as that used by \"erlc\".) + -I include_dir + pass the include_dir to TypEr + (The syntax of includes is the same as that used by \"erlc\".) + -pa dir + -pz dir + Set code path options to TypEr + (This is useful for files that use parse tranforms.) + --version (or -v) + prints the Typer version and exits + --help (or -h) + prints this message and exits + + Note: + * denotes that multiple occurrences of these options are possible. +">>, + io:put_chars(S), + erlang:halt(0). + +%%-------------------------------------------------------------------- +%% Handle messages. +%%-------------------------------------------------------------------- + +rcv_ext_types() -> + Self = self(), + Self ! {Self, done}, + rcv_ext_types(Self, []). + +rcv_ext_types(Self, ExtTypes) -> + receive + {Self, ext_types, ExtType} -> + rcv_ext_types(Self, [ExtType|ExtTypes]); + {Self, done} -> + lists:usort(ExtTypes) + end. + +%%-------------------------------------------------------------------- +%% A convenient abstraction of a Key-Value mapping data structure +%% specialized for the uses in this module +%%-------------------------------------------------------------------- + +-type map_dict() :: dict:dict(). + +-spec map__new() -> map_dict(). +map__new() -> + dict:new(). + +-spec map__insert({term(), term()}, map_dict()) -> map_dict(). +map__insert(Object, Map) -> + {Key, Value} = Object, + dict:store(Key, Value, Map). + +-spec map__lookup(term(), map_dict()) -> term(). +map__lookup(Key, Map) -> + try dict:fetch(Key, Map) catch error:_ -> none end. + +-spec map__from_list([{fa(), term()}]) -> map_dict(). +map__from_list(List) -> + dict:from_list(List). + +-spec map__remove(term(), map_dict()) -> map_dict(). +map__remove(Key, Dict) -> + dict:erase(Key, Dict). + +-spec map__fold(fun((term(), term(), term()) -> map_dict()), map_dict(), map_dict()) -> map_dict(). +map__fold(Fun, Acc0, Dict) -> + dict:fold(Fun, Acc0, Dict). |