diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_dataflow.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_dataflow.erl | 1329 |
1 files changed, 506 insertions, 823 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 2323889a51..ece162382f 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -24,16 +24,6 @@ -export([get_fun_types/5, get_warnings/5, format_args/3]). -%% Data structure interfaces. --export([state__add_warning/2, state__cleanup/1, - state__duplicate/1, dispose_state/1, - state__get_callgraph/1, state__get_races/1, - state__get_records/1, state__put_callgraph/2, - state__put_races/2, state__records_only/1, - state__find_function/2]). - --export_type([state/0]). - -include("dialyzer.hrl"). -import(erl_types, @@ -53,7 +43,7 @@ t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_any_atom/3, t_is_boolean/2, t_is_integer/2, t_is_list/1, - t_is_nil/2, t_is_none/1, t_is_none_or_unit/1, + t_is_nil/2, t_is_none/1, t_is_impossible/1, t_is_number/2, t_is_reference/2, t_is_pid/2, t_is_port/2, t_is_unit/1, t_limit/2, t_list/0, t_list_elements/2, @@ -85,37 +75,23 @@ -type curr_fun() :: 'undefined' | 'top' | mfa_or_funlbl(). --define(no_arg, no_arg). - -define(TYPE_LIMIT, 3). -define(BITS, 128). -%% Types with comment 'race' are due to dialyzer_races.erl. --record(state, {callgraph :: dialyzer_callgraph:callgraph() - | 'undefined', % race - codeserver :: dialyzer_codeserver:codeserver() - | 'undefined', % race - envs :: env_tab() - | 'undefined', % race - fun_tab :: fun_tab() - | 'undefined', % race - fun_homes :: dict:dict(label(), mfa()) - | 'undefined', % race - reachable_funs :: sets:set(label()) - | 'undefined', % race - plt :: dialyzer_plt:plt() - | 'undefined', % race - opaques :: [type()] - | 'undefined', % race - races = dialyzer_races:new() :: dialyzer_races:races(), +-record(state, {callgraph :: dialyzer_callgraph:callgraph(), + codeserver :: dialyzer_codeserver:codeserver(), + envs :: env_tab(), + fun_tab :: fun_tab(), + fun_homes :: dict:dict(label(), mfa()), + reachable_funs :: sets:set(label()), + plt :: dialyzer_plt:plt(), + opaques :: [type()], records = dict:new() :: types(), - tree_map :: dict:dict(label(), cerl:cerl()) - | 'undefined', % race + tree_map :: dict:dict(label(), cerl:cerl()), warning_mode = false :: boolean(), warnings = [] :: [raw_warning()], - work :: {[_], [_], sets:set()} - | 'undefined', % race + work :: {[_], [_], sets:set()}, module :: module(), curr_fun :: curr_fun() }). @@ -134,9 +110,7 @@ -type type_tab() :: #{key() => type()}. -type subst_tab() :: #{key() => cerl:cerl()}. -%% Exported Types - --opaque state() :: #state{}. +-type state() :: #state{}. %%-------------------------------------------------------------------- @@ -151,8 +125,7 @@ get_warnings(Tree, Plt, Callgraph, Codeserver, Records) -> State1 = analyze_module(Tree, Plt, Callgraph, Codeserver, Records, true), State2 = state__renew_warnings(state__get_warnings(State1), State1), - State3 = state__get_race_warnings(State2), - {State3#state.warnings, state__all_fun_types(State3)}. + {State2#state.warnings, state__all_fun_types(State2)}. -spec get_fun_types(cerl:c_module(), dialyzer_plt:plt(), dialyzer_callgraph:callgraph(), @@ -174,15 +147,13 @@ analyze_module(Tree, Plt, Callgraph, Codeserver, Records, GetWarnings) -> Module = cerl:atom_val(cerl:module_name(Tree)), TopFun = cerl:ann_c_fun([{label, top}], [], Tree), State = state__new(Callgraph, Codeserver, TopFun, Plt, Module, Records), - State1 = state__race_analysis(not GetWarnings, State), - State2 = analyze_loop(State1), + State1 = analyze_loop(State), case GetWarnings of true -> - State3 = state__set_warning_mode(State2), - State4 = analyze_loop(State3), - dialyzer_races:race(State4); + State2 = state__set_warning_mode(State1), + analyze_loop(State2); false -> - State2 + State1 end. analyze_loop(State) -> @@ -211,32 +182,16 @@ analyze_loop(State) -> Vars = cerl:fun_vars(Fun), Map1 = enter_type_lists(Vars, ArgTypes, Map), Body = cerl:fun_body(Fun), - FunLabel = get_label(Fun), - IsRaceAnalysisEnabled = is_race_analysis_enabled(State), - NewState3 = - case IsRaceAnalysisEnabled of - true -> - NewState2 = state__renew_curr_fun( - state__lookup_name(FunLabel, NewState1), FunLabel, - NewState1), - state__renew_race_list([], 0, NewState2); - false -> NewState1 - end, - {NewState4, _Map2, BodyType} = - traverse(Body, Map1, NewState3), + {NewState2, _Map2, BodyType} = + traverse(Body, Map1, NewState1), ?debug("Done analyzing: ~w:~ts\n", [NewState1#state.curr_fun, t_to_string(t_fun(ArgTypes, BodyType))]), - NewState5 = - case IsRaceAnalysisEnabled of - true -> renew_race_code(NewState4); - false -> NewState4 - end, - NewState6 = - state__update_fun_entry(Fun, ArgTypes, BodyType, NewState5), + NewState3 = + state__update_fun_entry(Fun, ArgTypes, BodyType, NewState2), ?debug("done adding stuff for ~tw\n", [state__lookup_name(get_label(Fun), State)]), - analyze_loop(NewState6) + analyze_loop(NewState3) end end end. @@ -295,63 +250,17 @@ traverse(Tree, Map, State) -> literal -> Type = literal_type(Tree), {State, Map, Type}; - module -> - handle_module(Tree, Map, State); primop -> - case cerl:atom_val(cerl:primop_name(Tree)) of - match_fail -> - {State, Map, t_none()}; - raise -> - {State, Map, t_none()}; - bs_init_writable -> - {State, Map, t_from_term(<<>>)}; - build_stacktrace -> - {State, Map, erl_bif_types:type(erlang, build_stacktrace, 0)}; - dialyzer_unknown -> - {State, Map, t_any()}; - recv_peek_message -> - {State, Map, t_product([t_boolean(), t_any()])}; - recv_wait_timeout -> - [Arg] = cerl:primop_args(Tree), - {State1, Map1, TimeoutType} = traverse(Arg, Map, State), - Opaques = State1#state.opaques, - case t_is_atom(TimeoutType, Opaques) andalso - t_atom_vals(TimeoutType, Opaques) =:= ['infinity'] of - true -> - {State1, Map1, t_boolean()}; - false -> - {State1, Map1, t_boolean()} - end; - remove_message -> - {State, Map, t_any()}; - timeout -> - {State, Map, t_any()}; - Other -> erlang:error({'Unsupported primop', Other}) - end; - 'receive' -> - handle_receive(Tree, Map, State); + handle_primop(Tree, Map, State); seq -> Arg = cerl:seq_arg(Tree), Body = cerl:seq_body(Tree), {State1, Map1, ArgType} = SMA = traverse(Arg, Map, State), - case t_is_none_or_unit(ArgType) of + case t_is_impossible(ArgType) of true -> SMA; false -> - State2 = - case - t_is_any(ArgType) - orelse t_is_simple(ArgType, State) - orelse is_call_to_send(Arg) - orelse is_lc_simple_list(Arg, ArgType, State) - of - true -> % do not warn in these cases - State1; - false -> - state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg, - {unmatched_return, - [format_type(ArgType, State1)]}) - end, + State2 = maybe_warn_unmatched_return(Arg, ArgType, State1), traverse(Body, Map1, State2) end; 'try' -> @@ -386,6 +295,27 @@ traverse_list([Tree|Tail], Map, State, Acc) -> traverse_list([], Map, State, Acc) -> {State, Map, lists:reverse(Acc)}. +maybe_warn_unmatched_return(Arg, ArgType, State) -> + case state__warning_mode(State) of + false -> + State; + true -> + %% Warn when return values such as 'ok' | {'error',any()} + %% are ignored. Don't warn when a single value such as 'ok' + %% is returned. + case t_is_any(ArgType) orelse + t_is_simple(ArgType, State) orelse + is_call_to_send(Arg) orelse + is_lc_simple_list(Arg, ArgType, State) of + true -> + State; + false -> + state__add_warning(State, ?WARN_UNMATCHED_RETURN, Arg, + {unmatched_return, + [format_type(ArgType, State)]}) + end + end. + %%________________________________________ %% %% Special instructions @@ -550,16 +480,6 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], ?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, State), - Location = get_location(Tree), - dialyzer_races:store_race_call(Fun, ArgTypes, Args, - {File, Location}, State); - false -> State - end, FailedConj = any_none([RetWithoutLocal|NewArgTypes]), IsFailBif = t_is_none(BifRange(BifArgs)), IsFailSig = t_is_none(SigRange), @@ -581,7 +501,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], %% This Msg will be post_processed by dialyzer_succ_typings Msg = {contract_range, [Contract, M1, F1, A1, ArgStrings, CRet]}, - state__add_warning(State1, ?WARN_CONTRACT_RANGE, Tree, Msg); + state__add_warning(State, ?WARN_CONTRACT_RANGE, Tree, Msg); false -> FailedSig = any_none(NewArgsSig), FailedContract = @@ -592,7 +512,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], FailReason = apply_fail_reason(FailedSig, FailedBif, FailedContract), Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig, - Contr, CArgs, State1, FailReason, Opaques), + Contr, CArgs, State, FailReason, Opaques), WarnType = case Msg of {call, _} -> ?WARN_FAILING_CALL; {apply, _} -> ?WARN_FAILING_CALL; @@ -614,9 +534,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Tree end, Frc = {erlang, is_record, 3} =:= state__lookup_name(Fun, State), - state__add_warning(State1, WarnType, LocTree, Msg, Frc) + state__add_warning(State, WarnType, LocTree, Msg, Frc) end; - false -> State1 + false -> State end, State3 = case TypeOfApply of @@ -767,17 +687,17 @@ find_unknown(ContractOrSigList, ArgTypes, Opaques, NoneArgNs) -> is_opaque_type_test_problem(Fun, Args, ArgTypes, State) -> case Fun of - {erlang, FN, 1} when FN =:= is_atom; FN =:= is_boolean; - FN =:= is_binary; FN =:= is_bitstring; - FN =:= is_float; FN =:= is_function; - FN =:= is_integer; FN =:= is_list; - FN =:= is_number; FN =:= is_pid; FN =:= is_port; - FN =:= is_reference; FN =:= is_tuple; - FN =:= is_map -> - type_test_opaque_arg(Args, ArgTypes, State#state.opaques); {erlang, FN, 2} when FN =:= is_function -> type_test_opaque_arg(Args, ArgTypes, State#state.opaques); - _ -> no + {erlang, FN, 1} -> + case t_is_any(type_test_type(FN, 1)) of + true -> + no; + false -> + type_test_opaque_arg(Args, ArgTypes, State#state.opaques) + end; + _ -> + no end. type_test_opaque_arg([], [], _Opaques) -> @@ -857,7 +777,7 @@ handle_bitstr(Tree, Map, State) -> {State1, Map1, SizeType0} = traverse(Size, Map, State), {State2, Map2, ValType0} = traverse(Val, Map1, State1), case cerl:bitstr_bitsize(Tree) of - BitSz when BitSz =:= all orelse BitSz =:= utf -> + BitSz when BitSz =:= all; BitSz =:= utf -> ValType = case BitSz of all -> @@ -878,7 +798,7 @@ handle_bitstr(Tree, Map, State) -> false -> {State2, Map3, t_bitstr()} end; - BitSz when is_integer(BitSz) orelse BitSz =:= any -> + BitSz when is_integer(BitSz); BitSz =:= any -> SizeType = t_inf(SizeType0, t_non_neg_integer()), ValType = case BitstrType of @@ -1004,29 +924,18 @@ handle_case(Tree, Map, State) -> Arg = cerl:case_arg(Tree), Clauses = cerl:case_clauses(Tree), {State1, Map1, ArgType} = SMA = traverse(Arg, Map, State), - case t_is_none_or_unit(ArgType) of + case t_is_impossible(ArgType) of true -> SMA; false -> - State2 = - case is_race_analysis_enabled(State) of - true -> - {RaceList, RaceListSize} = get_race_list_and_size(State1), - state__renew_race_list([beg_case|RaceList], - RaceListSize + 1, State1); - false -> State1 - end, Map2 = join_maps_begin(Map1), {MapList, State3, Type, Warns} = - handle_clauses(Clauses, Arg, ArgType, ArgType, State2, - [], Map2, [], [], []), + handle_clauses(Clauses, Arg, ArgType, Map2, State1), %% 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)), + DoForce = not is_compiler_generated(cerl:get_ann(Tree)) orelse t_is_none(Type), State4 = lists:foldl(fun({T,R,M,F}, S) -> - state__add_warning( - S,T,R,M,F andalso (not SupressForced)) + state__add_warning(S, T, R, M, F andalso DoForce) end, State3, Warns), Map3 = join_maps_end(MapList, Map2), debug_pp_map(Map3), @@ -1054,99 +963,57 @@ handle_cons(Tree, Map, State) -> %%---------------------------------------- handle_let(Tree, Map, State) -> - IsRaceAnalysisEnabled = is_race_analysis_enabled(State), Arg = cerl:let_arg(Tree), Vars = cerl:let_vars(Tree), {Map0, State0} = case cerl:is_c_var(Arg) of true -> [Var] = Vars, - {enter_subst(Var, Arg, Map), - case IsRaceAnalysisEnabled of - true -> - {RaceList, RaceListSize} = get_race_list_and_size(State), - state__renew_race_list( - [dialyzer_races:let_tag_new(Var, Arg)|RaceList], - RaceListSize + 1, State); - false -> State - end}; + {enter_subst(Var, Arg, Map), State}; false -> {Map, State} end, Body = cerl:let_body(Tree), {State1, Map1, ArgTypes} = SMA = traverse(Arg, Map0, State0), - State2 = - case IsRaceAnalysisEnabled andalso cerl:is_c_call(Arg) of - true -> - Mod = cerl:call_module(Arg), - Name = cerl:call_name(Arg), - case cerl:is_literal(Mod) andalso - cerl:concrete(Mod) =:= ets andalso - cerl:is_literal(Name) andalso - cerl:concrete(Name) =:= new of - true -> renew_race_public_tables(Vars, State1); - false -> State1 - end; - false -> State1 - end, - case t_is_none_or_unit(ArgTypes) of + case t_is_impossible(ArgTypes) of true -> SMA; false -> Map2 = enter_type_lists(Vars, t_to_tlist(ArgTypes), Map1), - traverse(Body, Map2, State2) + traverse(Body, Map2, State1) end. %%---------------------------------------- -handle_module(Tree, Map, State) -> - %% By not including the variables in scope we can assure that we - %% will get the current function type when using the variables. - Defs = cerl:module_defs(Tree), - PartFun = fun({_Var, Fun}) -> - state__is_escaping(get_label(Fun), State) - end, - {Defs1, Defs2} = lists:partition(PartFun, Defs), - Letrec = cerl:c_letrec(Defs1, cerl:c_int(42)), - {State1, Map1, _FunTypes} = traverse(Letrec, Map, State), - %% Also add environments for the other top-level functions. - VarTypes = [{Var, state__fun_type(Fun, State1)} || {Var, Fun} <- Defs], - EnvMap = enter_type_list(VarTypes, Map), - FoldFun = fun({_Var, Fun}, AccState) -> - state__update_fun_env(Fun, EnvMap, AccState) - end, - State2 = lists:foldl(FoldFun, State1, Defs2), - {State2, Map1, t_any()}. - -%%---------------------------------------- - -handle_receive(Tree, Map, State) -> - Clauses = cerl:receive_clauses(Tree), - Timeout = cerl:receive_timeout(Tree), - State1 = - case is_race_analysis_enabled(State) of - true -> - {RaceList, RaceListSize} = get_race_list_and_size(State), - state__renew_race_list([beg_case|RaceList], - RaceListSize + 1, State); - false -> State - end, - {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), - {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 -> - {State4, Map2, ReceiveType}; - false -> - Action = cerl:receive_action(Tree), - {State5, Map3, ActionType} = traverse(Action, Map, State4), - Map4 = join_maps([Map3, Map1], Map), - Type = t_sup(ReceiveType, ActionType), - {State5, Map4, Type} +handle_primop(Tree, Map, State) -> + case cerl:atom_val(cerl:primop_name(Tree)) of + match_fail -> + {State, Map, t_none()}; + raise -> + {State, Map, t_none()}; + bs_init_writable -> + {State, Map, t_from_term(<<>>)}; + build_stacktrace -> + {State, Map, erl_bif_types:type(erlang, build_stacktrace, 0)}; + dialyzer_unknown -> + {State, Map, t_any()}; + recv_peek_message -> + {State, Map, t_product([t_boolean(), t_any()])}; + recv_wait_timeout -> + [Arg] = cerl:primop_args(Tree), + {State1, Map1, TimeoutType} = traverse(Arg, Map, State), + Opaques = State1#state.opaques, + case t_is_atom(TimeoutType, Opaques) andalso + t_atom_vals(TimeoutType, Opaques) =:= ['infinity'] of + true -> + {State1, Map1, t_boolean()}; + false -> + {State1, Map1, t_boolean()} + end; + remove_message -> + {State, Map, t_any()}; + nif_start -> + {State, Map, t_any()}; + Other -> + error({'Unsupported primop', Other}) end. %%---------------------------------------- @@ -1165,14 +1032,14 @@ handle_try(Tree, Map, State) -> true -> Map2 = mark_as_fresh(Vars, Map1), {SuccState, SuccMap, SuccType} = - case bind_pat_vars(Vars, TypeList, [], Map2, State1) of + case bind_pat_vars(Vars, TypeList, Map2, State1) of {error, _, _, _, _} -> {State1, map__new(), t_none()}; {SuccMap1, VarTypes} -> %% Try to bind the argument. Will only succeed if %% it is a simple structured term. SuccMap2 = - case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [], + case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], SuccMap1, State1) of {error, _, _, _, _} -> SuccMap1; {SM, _} -> SM @@ -1192,7 +1059,7 @@ handle_map(Tree,Map,State) -> Arg = cerl:map_arg(Tree), {State1, Map1, ArgType} = traverse(Arg, Map, State), ArgType1 = t_inf(t_map(), ArgType), - case t_is_none_or_unit(ArgType1) of + case t_is_impossible(ArgType1) of true -> {State1, Map1, ArgType1}; false -> @@ -1208,7 +1075,7 @@ handle_map(Tree,Map,State) -> try lists:foldl(InsertPair, ArgType1, TypePairs) of ResT -> BindT = t_map([{K, t_any()} || K <- ExactKeys]), - case bind_pat_vars_reverse([Arg], [BindT], [], Map2, State2) of + case bind_pat_vars_reverse([Arg], [BindT], Map2, State2) of {error, _, _, _, _} -> {State2, Map2, ResT}; {Map3, _} -> {State2, Map3, ResT} end @@ -1269,7 +1136,7 @@ handle_tuple(Tree, Map, State) -> {State2, Map1, t_none()}; false -> case bind_pat_vars(Elements, t_tuple_args(RecType), - [], Map1, State1) of + Map1, State1) of {error, bind, ErrorPat, ErrorType, _} -> Msg = {record_constr, [TagVal, format_patterns(ErrorPat), @@ -1322,229 +1189,209 @@ field_name(Elements, ErrorPat, FieldNames) -> %%---------------------------------------- %% Clauses %% -handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, - Acc, ClauseAcc, WarnAcc0) -> - IsRaceAnalysisEnabled = is_race_analysis_enabled(State), - State1 = - case IsRaceAnalysisEnabled of - true -> - {RaceList, RaceListSize} = get_race_list_and_size(State), - state__renew_race_list( - [dialyzer_races:beg_clause_new(Arg, cerl:clause_pats(C), - cerl:clause_guard(C))| - RaceList], RaceListSize + 1, - State); - false -> State - end, - {State2, ClauseMap, BodyType, NewArgType, WarnAcc} = - do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1, WarnAcc0), - {NewClauseAcc, State3} = - case IsRaceAnalysisEnabled of - true -> - {RaceList1, RaceListSize1} = get_race_list_and_size(State2), - EndClause = dialyzer_races:end_clause_new(Arg, cerl:clause_pats(C), - cerl:clause_guard(C)), - {[EndClause|ClauseAcc], - state__renew_race_list([EndClause|RaceList1], - RaceListSize1 + 1, State2)}; - false -> {ClauseAcc, State2} - end, - {NewCaseTypes, NewAcc} = - case t_is_none(BodyType) of - true -> {CaseTypes, Acc}; - false -> {[BodyType|CaseTypes], [ClauseMap|Acc]} - end, - handle_clauses(Left, Arg, NewArgType, OrigArgType, State3, - NewCaseTypes, MapIn, NewAcc, NewClauseAcc, WarnAcc); -handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, - ClauseAcc, WarnAcc) -> - State1 = - case is_race_analysis_enabled(State) of - true -> - {RaceList, RaceListSize} = get_race_list_and_size(State), - state__renew_race_list( - [dialyzer_races:end_case_new(ClauseAcc)|RaceList], - RaceListSize + 1, State); - false -> State - end, - {lists:reverse(Acc), State1, t_sup(CaseTypes), WarnAcc}. -do_clause(C, Arg, ArgType0, OrigArgType, Map, State, Warns) -> +handle_clauses(Cs, Arg, ArgType, Map, State) -> + handle_clauses(Cs, Arg, ArgType, ArgType, Map, State, [], [], []). + +handle_clauses([C|Cs], Arg, ArgType, OrigArgType, MapIn, State, + CaseTypes, Acc, WarnAcc0) -> + {State1, ClauseMap, BodyType, NewArgType, WarnAcc} = + do_clause(C, Arg, ArgType, OrigArgType, MapIn, State, WarnAcc0), + case t_is_none(BodyType) of + true -> + handle_clauses(Cs, Arg, NewArgType, OrigArgType, MapIn, State1, + CaseTypes, Acc, WarnAcc); + false -> + handle_clauses(Cs, Arg, NewArgType, OrigArgType, MapIn, State1, + [BodyType|CaseTypes], [ClauseMap|Acc], WarnAcc) + end; +handle_clauses([], _Arg, _ArgType, _OrigArgType, _MapIn, State, + CaseTypes, Acc, WarnAcc) -> + {lists:reverse(Acc), State, t_sup(CaseTypes), WarnAcc}. + +%% +%% Process one clause. +%% +%% ArgType is the current type of the case argument; the types for +%% all previously matched clauses have been subtracted from it. +%% +%% OrigArgType is the original type of the case argument. We need it +%% to produce better warnings when a clause does not match. +%% +do_clause(C, Arg, ArgType, OrigArgType, Map, State, Warns) -> Pats = cerl:clause_pats(C), - Guard = cerl:clause_guard(C), - Body = cerl:clause_body(C), - State1 = - case is_race_analysis_enabled(State) of - true -> - state__renew_fun_args(Pats, State); - false -> State - end, Map0 = mark_as_fresh(Pats, Map), - Map1 = if Arg =:= ?no_arg -> Map0; - true -> bind_subst(Arg, Pats, Map0) - end, + Map1 = bind_subst(Arg, Pats, Map0), + + %% Try to bind the pattern to the case argument. BindRes = - case t_is_none(ArgType0) of + case t_is_none(ArgType) of true -> - {error, bind, Pats, ArgType0, ArgType0}; + {error, maybe_covered, OrigArgType, ignore, ignore}; false -> - ArgTypes = - case t_is_any(ArgType0) of - true -> [ArgType0 || _ <- Pats]; - false -> t_to_tlist(ArgType0) - end, - bind_pat_vars(Pats, ArgTypes, [], Map1, State1) + ArgTypes = get_arg_list(ArgType, Pats), + bind_pat_vars(Pats, ArgTypes, Map1, State) end, + + %% Test whether the binding succeeded. case BindRes of - {error, ErrorType, NewPats, Type, OpaqueTerm} -> + {error, _ErrorType, _NewPats, _Type, _OpaqueTerm} -> ?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, Warns}; - true -> - {Msg, Force} = - case t_is_none(ArgType0) of - true -> - %% See if this is covered by an earlier clause or if it - %% simply cannot match - OrigArgTypes = - case t_is_any(OrigArgType) of - 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 BindResOrig of - {error, bind, _, _, _} -> pattern_match; - {error, record, _, _, _} -> record_match; - {error, opaque, _, _, _} -> opaque_match; - {_, _} -> pattern_match_cov - end, - 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 suppress this. A real Hack(tm) - Force0 = - case is_compiler_generated(cerl:get_ann(C)) of - true -> - case Pats of - [Pat] -> - case cerl:is_c_cons(Pat) of - true -> - not (cerl:is_c_var(cerl:cons_hd(Pat)) andalso - cerl:is_c_var(cerl:cons_tl(Pat)) andalso - cerl:is_literal(Guard) andalso - (cerl:concrete(Guard) =:= true)); - false -> - true - end; - [Pat0, Pat1] -> % binary comprehension - case cerl:is_c_cons(Pat0) of - true -> - not (cerl:is_c_var(cerl:cons_hd(Pat0)) andalso - cerl:is_c_var(cerl:cons_tl(Pat0)) andalso - cerl:is_c_var(Pat1) andalso - cerl:is_literal(Guard) andalso - (cerl:concrete(Guard) =:= true)); - false -> - true - end; - _ -> true - end; - false -> - true - end, - PatString = - case ErrorType of - bind -> format_patterns(Pats); - record -> format_patterns(NewPats); - opaque -> format_patterns(NewPats) - end, - PatTypes = case ErrorType of - bind -> [PatString, format_type(ArgType0, State1)]; - record -> [PatString, format_type(Type, State1)]; - opaque -> [PatString, format_type(Type, State1), - format_type(OpaqueTerm, State1)] - end, - FailedTag = case ErrorType of - bind -> pattern_match; - record -> record_match; - opaque -> opaque_match - end, - {{FailedTag, PatTypes}, Force0} - end, - WarnType = case Msg of - {opaque_match, _} -> ?WARN_OPAQUE; - {pattern_match, _} -> ?WARN_MATCHING; - {record_match, _} -> ?WARN_MATCHING; - {pattern_match_cov, _} -> ?WARN_MATCHING - end, - {State1, Map, t_none(), ArgType0, [{WarnType, C, Msg, Force}|Warns]} - end; + [cerl_prettypr:format(C), format_type(ArgType, State)]), + NewWarns = + case state__warning_mode(State) of + false -> + Warns; + true -> + Warn = clause_error(State, Map1, BindRes, C, Pats, ArgType), + [Warn|Warns] + end, + {State, Map, t_none(), ArgType, NewWarns}; {Map2, PatTypes} -> + %% Try to bind the argument. Will only succeed if + %% it is a simple structured term. Map3 = - case Arg =:= ?no_arg of - true -> Map2; - false -> - %% Try to bind the argument. Will only succeed if - %% it is a simple structured term. - case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], - [], Map2, State1) of - {error, _, _, _, _} -> Map2; - {NewMap, _} -> NewMap - end + case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], + Map2, State) of + {error, _, _, _, _} -> Map2; + {NewMap, _} -> NewMap end, - NewArgType = - case Arg =:= ?no_arg of - true -> ArgType0; - false -> - GenType = dialyzer_typesig:get_safe_underapprox(Pats, Guard), - t_subtract(t_product(t_to_tlist(ArgType0)), GenType) - end, - case bind_guard(Guard, Map3, State1) of + + %% Subtract the matched type from the case argument. That will + %% allow us to find clauses that are covered by previous + %% clauses. + Guard = cerl:clause_guard(C), + GenType = dialyzer_typesig:get_safe_underapprox(Pats, Guard), + NewArgType = t_subtract(t_product(t_to_tlist(ArgType)), GenType), + + %% Now test whether the guard will succeed. + case bind_guard(Guard, Map3, State) of {error, Reason} -> ?debug("Failed guard: ~ts\n", [cerl_prettypr:format(C, [{hook, cerl_typean:pp_hook()}])]), - PatString = format_patterns(Pats), - DefaultMsg = - case Pats =:= [] of - true -> {guard_fail, []}; - false -> - {guard_fail_pat, [PatString, format_type(ArgType0, State1)]} - end, - Warn = - case Reason of - none -> {?WARN_MATCHING, C, DefaultMsg, false}; - {FailGuard, Msg} -> - case is_compiler_generated(cerl:get_ann(FailGuard)) of - false -> - WarnType = case Msg of - {guard_fail, _} -> ?WARN_MATCHING; - {neg_guard_fail, _} -> ?WARN_MATCHING; - {opaque_guard, _} -> ?WARN_OPAQUE - end, - {WarnType, FailGuard, Msg, false}; - true -> - {?WARN_MATCHING, C, Msg, false} - end - end, - {State1, Map, t_none(), NewArgType, [Warn|Warns]}; + Warn = clause_guard_error(State, Reason, C, Pats, ArgType), + {State, Map, t_none(), NewArgType, [Warn|Warns]}; Map4 -> - {RetState, RetMap, BodyType} = traverse(Body, Map4, State1), + Body = cerl:clause_body(C), + {RetState, RetMap, BodyType} = traverse(Body, Map4, State), {RetState, RetMap, BodyType, NewArgType, Warns} end end. +clause_error(State, Map, {error, maybe_covered, OrigArgType, _, _}, C, Pats, _) -> + %% This clause is covered by previous clauses, but it is possible + %% that it would never match anyway. Find out by matching the + %% original argument types of the case. + OrigArgTypes = get_arg_list(OrigArgType, Pats), + Msg = + case bind_pat_vars(Pats, OrigArgTypes, Map, State) of + {_, _} -> + %% The pattern would match if it had not been covered. + PatString = format_patterns(Pats), + ArgTypeString = format_type(OrigArgType, State), + {pattern_match_cov, [PatString, ArgTypeString]}; + {error, ErrorType, _, _, OpaqueTerm} -> + %% This pattern can never match. + failed_msg(State, ErrorType, Pats, OrigArgType, + Pats, OrigArgType, OpaqueTerm) + end, + Force = false, + clause_error_warning(Msg, Force, C); +clause_error(State, _Map, BindRes, C, Pats, ArgType) -> + %% This clause will never match. Always produce a warning + %% unless it is the default clause in a list comprehension + %% without any filters. + Force = not is_lc_default_clause(C), + {error, ErrorType, NewPats, NewType, OpaqueTerm} = BindRes, + Msg = failed_msg(State, ErrorType, Pats, ArgType, NewPats, NewType, OpaqueTerm), + clause_error_warning(Msg, Force, C). + +failed_msg(State, ErrorType, Pats, Type, NewPats, NewType, OpaqueTerm) -> + case ErrorType of + bind -> + {pattern_match, [format_patterns(Pats), format_type(Type, State)]}; + record -> + {record_match, [format_patterns(NewPats), format_type(NewType, State)]}; + opaque -> + {opaque_match, [format_patterns(NewPats), format_type(NewType, State), + format_type(OpaqueTerm, State)]} + end. + +clause_error_warning(Msg, Force, C) -> + {warn_type(Msg), C, Msg, Force}. + +warn_type({Tag, _}) -> + case Tag of + guard_fail -> ?WARN_MATCHING; + neg_guard_fail -> ?WARN_MATCHING; + opaque_guard -> ?WARN_OPAQUE; + opaque_match -> ?WARN_OPAQUE; + pattern_match -> ?WARN_MATCHING; + pattern_match_cov -> ?WARN_MATCHING; + record_match -> ?WARN_MATCHING + end. + +get_arg_list(ArgTypes, Pats) -> + case t_is_any(ArgTypes) of + true -> + [ArgTypes || _ <- Pats]; + false -> + t_to_tlist(ArgTypes) + end. + +%% Test whether this clause is the default clause for a list +%% or binary comprehension without any filters. +is_lc_default_clause(C) -> + case is_compiler_generated(cerl:get_ann(C)) of + false -> + false; + true -> + case cerl:clause_pats(C) of + [Pat] -> % list comprehension + cerl:is_c_cons(Pat) andalso + cerl:is_c_var(cerl:cons_hd(Pat)) andalso + cerl:is_c_var(cerl:cons_tl(Pat)) andalso + does_guard_succeed(C); + [Pat0, Pat1] -> % binary comprehension + cerl:is_c_cons(Pat0) andalso + cerl:is_c_var(cerl:cons_hd(Pat0)) andalso + cerl:is_c_var(cerl:cons_tl(Pat0)) andalso + cerl:is_c_var(Pat1) andalso + does_guard_succeed(C); + _ -> + %% This should not happen with code generated by the + %% standard Erlang compiler. It can happen if the code was + %% generate by another code generator (such as Elixir) or a + %% parse transform. + false + end + end. + +does_guard_succeed(C) -> + Guard = cerl:clause_guard(C), + cerl:is_literal(Guard) andalso cerl:concrete(Guard) =:= true. + +clause_guard_error(State, Reason, C, Pats, ArgType) -> + case Reason of + none -> + Msg = + case Pats of + [] -> + {guard_fail, []}; + [_|_] -> + PatString = format_patterns(Pats), + {guard_fail_pat, [PatString, format_type(ArgType, State)]} + end, + {?WARN_MATCHING, C, Msg, false}; + {FailGuard, Msg} -> + case is_compiler_generated(cerl:get_ann(FailGuard)) of + false -> + {warn_type(Msg), FailGuard, Msg, false}; + true -> + {?WARN_MATCHING, C, Msg, false} + end + end. + bind_subst(Arg, Pats, Map) -> case cerl:type(Arg) of values -> @@ -1575,27 +1422,23 @@ bind_subst_list([], [], Map) -> %% Patterns %% -bind_pat_vars(Pats, Types, Acc, Map, State) -> - try - bind_pat_vars(Pats, Types, Acc, Map, State, false) - catch - throw:Error -> - %% Error = {error, bind | opaque | record, ErrorPats, ErrorType} - Error - end. +bind_pat_vars(Pats, Types, Map, State) -> + bind_pat_vars(Pats, Types, Map, State, false). -bind_pat_vars_reverse(Pats, Types, Acc, Map, State) -> +bind_pat_vars_reverse(Pats, Types, Map, State) -> + bind_pat_vars(Pats, Types, Map, State, true). + +bind_pat_vars(Pats, Types, Map, State, Rev) -> try - bind_pat_vars(Pats, Types, Acc, Map, State, true) + do_bind_pat_vars(Pats, Types, Map, State, Rev, []) catch - throw:Error -> + throw:Error -> %% Error = {error, bind | opaque | record, ErrorPats, ErrorType} Error end. -bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> - ?debug("Binding pat: ~tw to ~ts\n", [cerl:type(Pat), format_type(Type, State)] -), +do_bind_pat_vars([Pat|Pats], [Type|Types], Map, State, Rev, Acc) -> + ?debug("Binding pat: ~tw to ~ts\n", [cerl:type(Pat), format_type(Type, State)]), Opaques = State#state.opaques, {NewMap, TypeOut} = case cerl:type(Pat) of @@ -1605,157 +1448,48 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> AliasPat = dialyzer_utils:refold_pattern(cerl:alias_pat(Pat)), Var = cerl:alias_var(Pat), Map1 = enter_subst(Var, AliasPat, Map), - {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [], - Map1, State, Rev), + {Map2, [PatType]} = do_bind_pat_vars([AliasPat], [Type], + Map1, State, Rev, []), {enter_type(Var, PatType, Map2), PatType}; binary -> - %% Cannot bind the binary if we are in reverse match since - %% binary patterns and binary construction are not symmetric. case Rev of - true -> {Map, t_bitstr()}; - false -> - BinType = t_inf(t_bitstr(), Type, Opaques), - case t_is_none(BinType) of - true -> - case t_find_opaque_mismatch(t_bitstr(), Type, Opaques) of - {ok, T1, T2} -> - bind_error([Pat], T1, T2, opaque); - error -> - bind_error([Pat], Type, t_none(), bind) - end; - false -> - Segs = cerl:binary_segments(Pat), - {Map1, SegTypes} = bind_bin_segs(Segs, BinType, Map, State), - {Map1, t_bitstr_concat(SegTypes)} - end - end; - cons -> - Cons = t_inf(Type, t_cons(), Opaques), - case t_is_none(Cons) of true -> - bind_opaque_pats(t_cons(), Type, Pat, State); + %% Cannot bind the binary if we are in reverse match since + %% binary patterns and binary construction are not + %% symmetric. + {Map, t_bitstr()}; false -> - {Map1, [HdType, TlType]} = - bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], - [t_cons_hd(Cons, Opaques), - t_cons_tl(Cons, Opaques)], - [], Map, State, Rev), - {Map1, t_cons(HdType, TlType)} + BinType = bind_checked_inf(Pat, t_bitstr(), Type, Opaques), + Segs = cerl:binary_segments(Pat), + {Map1, SegTypes} = bind_bin_segs(Segs, BinType, Map, State), + {Map1, t_bitstr_concat(SegTypes)} end; + cons -> + Cons = bind_checked_inf(Pat, t_cons(), Type, Opaques), + {Map1, [HdType, TlType]} = + do_bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], + [t_cons_hd(Cons, Opaques), + t_cons_tl(Cons, Opaques)], + Map, State, Rev, []), + {Map1, t_cons(HdType, TlType)}; literal -> Pat0 = dialyzer_utils:refold_pattern(Pat), case cerl:is_literal(Pat0) of true -> - Literal = literal_type(Pat), - case t_is_none(t_inf(Literal, Type, Opaques)) of - true -> - bind_opaque_pats(Literal, Type, Pat, State); - false -> {Map, Literal} - end; + LiteralType = bind_checked_inf(Pat, literal_type(Pat), Type, Opaques), + {Map, LiteralType}; false -> - %% Retry with the unfolded pattern - {Map1, [PatType]} - = bind_pat_vars([Pat0], [Type], [], Map, State, Rev), + {Map1, [PatType]} = do_bind_pat_vars([Pat0], [Type], Map, State, Rev, []), {Map1, PatType} end; map -> - MapT = t_inf(Type, t_map(), Opaques), - case t_is_none(MapT) of - true -> - bind_opaque_pats(t_map(), Type, Pat, State); - false -> - case Rev of - %% TODO: Reverse matching (propagating a matched subset back to a value) - true -> {Map, MapT}; - false -> - FoldFun = - fun(Pair, {MapAcc, ListAcc}) -> - %% Only exact (:=) can appear in patterns - exact = cerl:concrete(cerl:map_pair_op(Pair)), - Key = cerl:map_pair_key(Pair), - KeyType = - case cerl:type(Key) of - var -> - case state__lookup_type_for_letrec(Key, State) of - error -> lookup_type(Key, MapAcc); - {ok, RecType} -> RecType - end; - literal -> - literal_type(Key) - end, - Bind = erl_types:t_map_get(KeyType, MapT), - {MapAcc1, [ValType]} = - bind_pat_vars([cerl:map_pair_val(Pair)], - [Bind], [], MapAcc, State, Rev), - case t_is_singleton(KeyType, Opaques) of - true -> {MapAcc1, [{KeyType, ValType}|ListAcc]}; - false -> {MapAcc1, ListAcc} - end - end, - {Map1, Pairs} = lists:foldl(FoldFun, {Map, []}, cerl:map_es(Pat)), - {Map1, t_inf(MapT, t_map(Pairs))} - end - end; + bind_map(Pat, Type, Map, State, Opaques, Rev); tuple -> - Es = cerl:tuple_es(Pat), - {TypedRecord, Prototype} = - case Es of - [] -> {false, t_tuple([])}; - [Tag|Left] -> - case cerl:is_c_atom(Tag) andalso is_literal_record(Pat) of - true -> - TagAtom = cerl:atom_val(Tag), - case state__lookup_record(TagAtom, length(Left), State) of - error -> {false, t_tuple(length(Es))}; - {ok, Record, _FieldNames} -> - [_Head|AnyTail] = [t_any() || _ <- Es], - UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]), - {not t_is_equal(Record, UntypedRecord), Record} - end; - false -> {false, t_tuple(length(Es))} - end - end, - Tuple = t_inf(Prototype, Type, Opaques), - case t_is_none(Tuple) of - true -> - bind_opaque_pats(Prototype, Type, Pat, State); - false -> - SubTuples = t_tuple_subtypes(Tuple, Opaques), - %% Need to call the top function to get the try-catch wrapper - MapJ = join_maps_begin(Map), - Results = - case Rev of - true -> - [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple, Opaques), - [], MapJ, State) - || SubTuple <- SubTuples]; - false -> - [bind_pat_vars(Es, t_tuple_args(SubTuple, Opaques), [], - MapJ, State) - || SubTuple <- SubTuples] - end, - case lists:keyfind(opaque, 2, Results) of - {error, opaque, _PatList, _Type, Opaque} -> - bind_error([Pat], Tuple, Opaque, opaque); - false -> - case [M || {M, _} <- Results, M =/= error] of - [] -> - case TypedRecord of - true -> bind_error([Pat], Tuple, Prototype, record); - false -> bind_error([Pat], Tuple, t_none(), bind) - end; - Maps -> - Map1 = join_maps_end(Maps, MapJ), - TupleType = t_sup([t_tuple(EsTypes) - || {M, EsTypes} <- Results, M =/= error]), - {Map1, TupleType} - end - end - end; + bind_tuple(Pat, Type, Map, State, Opaques, Rev); values -> Es = cerl:values_es(Pat), - {Map1, EsTypes} = - bind_pat_vars(Es, t_to_tlist(Type), [], Map, State, Rev), + {Map1, EsTypes} = do_bind_pat_vars(Es, t_to_tlist(Type), + Map, State, Rev, []), {Map1, t_product(EsTypes)}; var -> VarType1 = @@ -1764,28 +1498,100 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> {ok, RecType} -> RecType end, %% Must do inf when binding args to pats. Vars in pats are fresh. - VarType2 = t_inf(VarType1, Type, Opaques), - case t_is_none(VarType2) of - true -> - case t_find_opaque_mismatch(VarType1, Type, Opaques) of - {ok, T1, T2} -> - bind_error([Pat], T1, T2, opaque); - error -> - bind_error([Pat], Type, t_none(), bind) - end; - false -> - Map1 = enter_type(Pat, VarType2, Map), - {Map1, VarType2} - end; + VarType2 = bind_checked_inf(Pat, VarType1, Type, Opaques), + Map1 = enter_type(Pat, VarType2, Map), + {Map1, VarType2}; _Other -> %% Catch all is needed when binding args to pats ?debug("Failed match for ~p\n", [_Other]), + Rev = true, % Assertion. bind_error([Pat], Type, t_none(), bind) end, - bind_pat_vars(PatLeft, TypeLeft, [TypeOut|Acc], NewMap, State, Rev); -bind_pat_vars([], [], Acc, Map, _State, _Rev) -> + do_bind_pat_vars(Pats, Types, NewMap, State, Rev, [TypeOut|Acc]); +do_bind_pat_vars([], [], Map, _State, _Rev, Acc) -> {Map, lists:reverse(Acc)}. +bind_map(Pat, Type, Map, State, Opaques, Rev) -> + MapT = bind_checked_inf(Pat, t_map(), Type, Opaques), + case Rev of + %% TODO: Reverse matching (propagating a matched subset back to a value). + true -> + {Map, MapT}; + false -> + FoldFun = + fun(Pair, {MapAcc, ListAcc}) -> + %% Only exact (:=) can appear in patterns. + exact = cerl:concrete(cerl:map_pair_op(Pair)), + Key = cerl:map_pair_key(Pair), + KeyType = + case cerl:type(Key) of + var -> + case state__lookup_type_for_letrec(Key, State) of + error -> lookup_type(Key, MapAcc); + {ok, RecType} -> RecType + end; + literal -> + literal_type(Key) + end, + Bind = erl_types:t_map_get(KeyType, MapT), + {MapAcc1, [ValType]} = + do_bind_pat_vars([cerl:map_pair_val(Pair)], + [Bind], MapAcc, State, Rev, []), + case t_is_singleton(KeyType, Opaques) of + true -> {MapAcc1, [{KeyType, ValType}|ListAcc]}; + false -> {MapAcc1, ListAcc} + end + end, + {Map1, Pairs} = lists:foldl(FoldFun, {Map, []}, cerl:map_es(Pat)), + {Map1, t_inf(MapT, t_map(Pairs))} + end. + +bind_tuple(Pat, Type, Map, State, Opaques, Rev) -> + Es = cerl:tuple_es(Pat), + {IsTypedRecord, Prototype} = + case Es of + [] -> + {false, t_tuple([])}; + [Tag|Tags] -> + case cerl:is_c_atom(Tag) andalso is_literal_record(Pat) of + true -> + Any = t_any(), + [_Head|AnyTail] = [Any || _ <- Es], + UntypedRecord = t_tuple([Tag|AnyTail]), + case state__lookup_record(cerl:atom_val(Tag), length(Tags), State) of + error -> + {false, UntypedRecord}; + {ok, Record, _FieldNames} -> + {not t_is_equal(Record, UntypedRecord), Record} + end; + false -> + {false, t_tuple(length(Es))} + end + end, + Tuple = bind_checked_inf(Pat, Prototype, Type, Opaques), + SubTuples = t_tuple_subtypes(Tuple, Opaques), + MapJ = join_maps_begin(Map), + %% Need to call the top function to get the try-catch wrapper. + Results = [bind_pat_vars(Es, t_tuple_args(SubTuple, Opaques), MapJ, State, Rev) || + SubTuple <- SubTuples], + case lists:keyfind(opaque, 2, Results) of + {error, opaque, _PatList, _Type, Opaque} -> + bind_error([Pat], Tuple, Opaque, opaque); + false -> + case [M || {M, _} <- Results, M =/= error] of + [] -> + case IsTypedRecord of + true -> bind_error([Pat], Tuple, Prototype, record); + false -> bind_error([Pat], Tuple, t_none(), bind) + end; + Maps -> + Map1 = join_maps_end(Maps, MapJ), + TupleType = t_sup([t_tuple(EsTypes) || + {M, EsTypes} <- Results, M =/= error]), + {Map1, TupleType} + end + end. + bind_bin_segs(BinSegs, BinType, Map, State) -> bind_bin_segs(BinSegs, BinType, [], Map, State). @@ -1793,22 +1599,24 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) -> Val = cerl:bitstr_val(Seg), SegType = cerl:concrete(cerl:bitstr_type(Seg)), UnitVal = cerl:concrete(cerl:bitstr_unit(Seg)), - case cerl:bitstr_bitsize(Seg) of + Size = cerl:bitstr_size(Seg), + case bitstr_bitsize_type(Size) of all -> binary = SegType, [] = Segs, %% just an assert T = t_inf(t_bitstr(UnitVal, 0), BinType), - {Map1, [Type]} = bind_pat_vars([Val], [T], [], Map, State, false), + {Map1, [Type]} = do_bind_pat_vars([Val], [T], Map, + State, false, []), Type1 = remove_local_opaque_types(Type, State#state.opaques), bind_bin_segs(Segs, t_bitstr(0, 0), [Type1|Acc], Map1, State); - utf -> % XXX: possibly can be strengthened + utf -> % XXX: can possibly be strengthened true = lists:member(SegType, [utf8, utf16, utf32]), - {Map1, [_]} = bind_pat_vars([Val], [t_integer()], [], Map, State, false), + {Map1, [_]} = do_bind_pat_vars([Val], [t_integer()], + Map, State, false, []), Type = t_binary(), bind_bin_segs(Segs, BinType, [Type|Acc], Map1, State); - BitSz when is_integer(BitSz) orelse BitSz =:= any -> - Size = cerl:bitstr_size(Seg), - {Map1, [SizeType]} = - bind_pat_vars([Size], [t_non_neg_integer()], [], Map, State, false), + any -> + {Map1, [SizeType]} = do_bind_pat_vars([Size], [t_non_neg_integer()], + Map, State, false, []), Opaques = State#state.opaques, NumberVals = t_number_vals(SizeType, Opaques), case t_contains_opaque(SizeType, Opaques) of @@ -1847,7 +1655,7 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) -> end end end, - {Map2, [_]} = bind_pat_vars([Val], [ValConstr], [], Map1, State, false), + {Map2, [_]} = do_bind_pat_vars([Val], [ValConstr], Map1, State, false, []), NewBinType = t_bitstr_match(Type, BinType), case t_is_none(NewBinType) of true -> bind_error([Seg], BinType, t_none(), bind); @@ -1857,6 +1665,34 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) -> bind_bin_segs([], _BinType, Acc, Map, _State) -> {Map, lists:reverse(Acc)}. +bitstr_bitsize_type(Size) -> + case cerl:is_literal(Size) of + true -> + case cerl:concrete(Size) of + all -> all; + undefined -> utf; + _ -> any + end; + false -> + any + end. + +%% Return the infimum (meet) of ExpectedType and Type if it describes a +%% possible value (not 'none' or 'unit'), otherwise raise a bind_error(). +bind_checked_inf(Pat, ExpectedType, Type, Opaques) -> + Inf = t_inf(ExpectedType, Type, Opaques), + case t_is_impossible(Inf) of + true -> + case t_find_opaque_mismatch(ExpectedType, Type, Opaques) of + {ok, T1, T2} -> + bind_error([Pat], T1, T2, opaque); + error -> + bind_error([Pat], Type, Inf, bind) + end; + false -> + Inf + end. + bind_error(Pats, Type, OpaqueType, Error0) -> Error = case {Error0, Pats} of {bind, [Pat]} -> @@ -1868,17 +1704,6 @@ bind_error(Pats, Type, OpaqueType, Error0) -> end, throw({error, Error, Pats, Type, OpaqueType}). --spec bind_opaque_pats(type(), type(), cerl:c_literal(), state()) -> - no_return(). - -bind_opaque_pats(GenType, Type, Pat, State) -> - case t_find_opaque_mismatch(GenType, Type, State#state.opaques) of - {ok, T1, T2} -> - bind_error([Pat], T1, T2, opaque); - error -> - bind_error([Pat], Type, t_none(), bind) - end. - %%---------------------------------------- %% Guards %% @@ -1923,9 +1748,7 @@ bind_guard(Guard, Map, Env, Eval, State) -> {{Map2, t_none()}, HE} end, BodyEnv = maps:put(get_label(Var), Arg, Env), - Wanted = case Eval of pos -> t_atom(true); neg -> t_atom(false); - dont_know -> t_any() end, - case t_is_none(t_inf(HandlerType, Wanted)) of + case t_is_none(guard_eval_inf(Eval, HandlerType)) of %% Handler won't save us; pretend it does not exist true -> bind_guard(cerl:try_body(Guard), Map, BodyEnv, Eval, State); false -> @@ -1981,13 +1804,7 @@ bind_guard(Guard, Map, Env, Eval, State) -> error -> ?debug("Did not find it\n", []), Type = lookup_type(Guard, Map), - Constr = - case Eval of - pos -> t_atom(true); - neg -> t_atom(false); - dont_know -> Type - end, - Inf = t_inf(Constr, Type), + Inf = guard_eval_inf(Eval, Type), {enter_type(Guard, Inf, Map), Inf}; {ok, Tree} -> ?debug("Found it\n", []), @@ -2003,17 +1820,9 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> cerl:atom_val(cerl:call_name(Guard)), cerl:call_arity(Guard)}, case MFA of - {erlang, F, 1} when F =:= is_atom; F =:= is_boolean; - F =:= is_binary; F =:= is_bitstring; - F =:= is_float; F =:= is_function; - F =:= is_integer; F =:= is_list; F =:= is_map; - F =:= is_number; F =:= is_pid; F =:= is_port; - F =:= is_reference; F =:= is_tuple -> - handle_guard_type_test(Guard, F, Map, Env, Eval, State); {erlang, is_function, 2} -> handle_guard_is_function(Guard, Map, Env, Eval, State); - MFA when (MFA =:= {erlang, internal_is_record, 3}) or - (MFA =:= {erlang, is_record, 3}) -> + {erlang, F, 3} when F =:= internal_is_record; F =:= is_record -> handle_guard_is_record(Guard, Map, Env, Eval, State); {erlang, '=:=', 2} -> handle_guard_eqeq(Guard, Map, Env, Eval, State); @@ -2028,8 +1837,14 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<'; Comp =:= '>'; Comp =:= '>=' -> handle_guard_comp(Guard, Comp, Map, Env, Eval, State); - _ -> - handle_guard_gen_fun(MFA, Guard, Map, Env, Eval, State) + {erlang, F, A} -> + TypeTestType = type_test_type(F, A), + case t_is_any(TypeTestType) of + true -> + handle_guard_gen_fun(MFA, Guard, Map, Env, Eval, State); + false -> + handle_guard_type_test(Guard, TypeTestType, Map, Env, Eval, State) + end end. handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> @@ -2047,12 +1862,7 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> false -> BifArgs = bif_args(M, F, A), Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As, Opaques), Map1), - Ret = - case Eval of - pos -> t_inf(t_atom(true), BifRet); - neg -> t_inf(t_atom(false), BifRet); - dont_know -> BifRet - end, + Ret = guard_eval_inf(Eval, BifRet), case t_is_none(Ret) of true -> case Eval =:= pos of @@ -2063,10 +1873,10 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> end end. -handle_guard_type_test(Guard, F, Map, Env, Eval, State) -> +handle_guard_type_test(Guard, TypeTestType, Map, Env, Eval, State) -> [Arg] = cerl:call_args(Guard), {Map1, ArgType} = bind_guard(Arg, Map, Env, dont_know, State), - case bind_type_test(Eval, F, ArgType, State) of + case bind_type_test(Eval, TypeTestType, ArgType, State) of error -> ?debug("Type test: ~w failed\n", [F]), signal_guard_fail(Eval, Guard, [ArgType], State); @@ -2076,23 +1886,7 @@ handle_guard_type_test(Guard, F, Map, Env, Eval, State) -> {enter_type(Arg, NewArgType, Map1), Ret} end. -bind_type_test(Eval, TypeTest, ArgType, State) -> - Type = case TypeTest of - is_atom -> t_atom(); - is_boolean -> t_boolean(); - is_binary -> t_binary(); - is_bitstring -> t_bitstr(); - is_float -> t_float(); - is_function -> t_fun(); - is_integer -> t_integer(); - is_list -> t_maybe_improper_list(); - is_map -> t_map(); - is_number -> t_number(); - is_pid -> t_pid(); - is_port -> t_port(); - is_reference -> t_reference(); - is_tuple -> t_tuple() - end, +bind_type_test(Eval, Type, ArgType, State) -> case Eval of pos -> Inf = t_inf(Type, ArgType, State#state.opaques), @@ -2110,6 +1904,27 @@ bind_type_test(Eval, TypeTest, ArgType, State) -> {ok, ArgType, t_boolean()} end. +type_test_type(TypeTest, 1) -> + case TypeTest of + is_atom -> t_atom(); + is_boolean -> t_boolean(); + is_binary -> t_binary(); + is_bitstring -> t_bitstr(); + is_float -> t_float(); + is_function -> t_fun(); + is_integer -> t_integer(); + is_list -> t_maybe_improper_list(); + is_map -> t_map(); + is_number -> t_number(); + is_pid -> t_pid(); + is_port -> t_port(); + is_reference -> t_reference(); + is_tuple -> t_tuple(); + _ -> t_any() % Not a known type test. + end; +type_test_type(_, _) -> + t_any(). + handle_guard_comp(Guard, Comp, Map, Env, Eval, State) -> Args = cerl:call_args(Guard), [Arg1, Arg2] = Args, @@ -2129,12 +1944,12 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) -> false when Eval =:= dont_know -> {Map, t_atom(false)}; false when Eval =:= neg -> {Map, t_atom(false)} end; - {{literal, Lit1}, var} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) -> + {{literal, Lit1}, var} when IsInt1, IsInt2, Eval =:= pos -> case bind_comp_literal_var(Lit1, Arg2, Type2, Comp, Map1, Opaques) of error -> signal_guard_fail(Eval, Guard, ArgTypes, State); {ok, NewMap} -> {NewMap, t_atom(true)} end; - {var, {literal, Lit2}} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) -> + {var, {literal, Lit2}} when IsInt1, IsInt2, Eval =:= pos -> case bind_comp_literal_var(Lit2, Arg1, Type1, invert_comp(Comp), Map1, Opaques) of error -> signal_guard_fail(Eval, Guard, ArgTypes, State); @@ -2313,11 +2128,7 @@ bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> OpArgs = erl_types:t_find_unknown_opaque(Type1, Type2, Opaques), case OpArgs =:= [] of true -> - case Eval of - pos -> {Map2, t_atom(true)}; - neg -> {Map2, t_atom(false)}; - dont_know -> {Map2, t_boolean()} - end; + {Map2, guard_eval_inf(Eval, t_boolean())}; false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State) end @@ -2600,7 +2411,7 @@ handle_guard_map(Guard, Map, Env, State) -> Arg = cerl:map_arg(Guard), {Map1, ArgType0} = bind_guard(Arg, Map, Env, dont_know, State), ArgType1 = t_inf(t_map(), ArgType0), - case t_is_none_or_unit(ArgType1) of + case t_is_impossible(ArgType1) of true -> {Map1, t_none()}; false -> {Map2, TypePairs} = bind_guard_map_pairs(Pairs, Map1, Env, State, []), @@ -2621,6 +2432,14 @@ bind_guard_map_pairs([Pair|Pairs], Map, Env, State, PairAcc) -> -type eval() :: 'pos' | 'neg' | 'dont_know'. +guard_eval_inf(Eval, Type) -> + Constr = case Eval of + pos -> t_atom(true); + neg -> t_atom(false); + dont_know -> Type + end, + t_inf(Constr, Type). + -spec signal_guard_fail(eval(), cerl:c_call(), [type()], state()) -> no_return(). @@ -2668,16 +2487,10 @@ signal_guard_failure(Eval, Guard, ArgTypes, Tag, State) -> end, throw({Tag, {LocTree, Msg}}). -is_infix_op({erlang, '=:=', 2}) -> true; -is_infix_op({erlang, '==', 2}) -> true; -is_infix_op({erlang, '=/=', 2}) -> true; -is_infix_op({erlang, '=/', 2}) -> true; -is_infix_op({erlang, '<', 2}) -> true; -is_infix_op({erlang, '=<', 2}) -> true; -is_infix_op({erlang, '>', 2}) -> true; -is_infix_op({erlang, '>=', 2}) -> true; -is_infix_op({M, F, A}) when is_atom(M), is_atom(F), - is_integer(A), 0 =< A, A =< 255 -> false. +is_infix_op({erlang, F, 2}) -> + erl_internal:comp_op(F, 2); +is_infix_op({_, _, _}) -> + false. bif_args(M, F, A) -> case erl_bif_types:arg_types(M, F, A) of @@ -2741,7 +2554,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], true -> Any = t_any(), [Any || _ <- Pats]; false -> t_to_tlist(ArgType) end, - case bind_pat_vars(Pats, ArgTypes, [], NewMap0, State) of + case bind_pat_vars(Pats, ArgTypes, NewMap0, State) of {error, _, _, _, _} -> none; {PatMap, _PatTypes} -> PatMap end @@ -2880,12 +2693,6 @@ enter_type_lists([Key|KeyTail], [Val|ValTail], Map) -> enter_type_lists([], [], Map) -> Map. -enter_type_list([{Key, Val}|Left], Map) -> - Map1 = enter_type(Key, Val, Map), - enter_type_list(Left, Map1); -enter_type_list([], Map) -> - Map. - enter_type(Key, Val, MS) -> case cerl:is_literal(Key) of true -> MS; @@ -3091,15 +2898,15 @@ state__new(Callgraph, Codeserver, Tree, Plt, Module, Records) -> dict:new(), Funs), #state{callgraph = Callgraph, codeserver = Codeserver, envs = Env, fun_tab = FunTab, fun_homes = FunHomes, opaques = Opaques, - plt = Plt, races = dialyzer_races:new(), records = Records, + plt = Plt, records = Records, warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, - module = Module, reachable_funs = sets:new()}. + module = Module, reachable_funs = sets:new([{version, 2}])}. state__warning_mode(#state{warning_mode = WM}) -> WM. state__set_warning_mode(#state{tree_map = TreeMap, fun_tab = FunTab, - races = Races, callgraph = Callgraph, + callgraph = Callgraph, reachable_funs = ReachableFuns} = State) -> ?debug("==========\nStarting warning pass\n==========\n", []), Funs = dict:fetch_keys(TreeMap), @@ -3109,37 +2916,11 @@ state__set_warning_mode(#state{tree_map = TreeMap, fun_tab = FunTab, dialyzer_callgraph:lookup_name(Fun, Callgraph) =/= error orelse sets:is_element(Fun, ReachableFuns)], State#state{work = init_work(Work), - fun_tab = FunTab, warning_mode = true, - races = dialyzer_races:put_race_analysis(true, Races)}. - -state__race_analysis(Analysis, #state{races = Races} = State) -> - State#state{races = dialyzer_races:put_race_analysis(Analysis, Races)}. - -state__renew_curr_fun(CurrFun, CurrFunLabel, - #state{races = Races} = State) -> - State#state{races = dialyzer_races:put_curr_fun(CurrFun, CurrFunLabel, - Races)}. - -state__renew_fun_args(Args, #state{races = Races} = State) -> - case state__warning_mode(State) of - true -> State; - false -> - State#state{races = dialyzer_races:put_fun_args(Args, Races)} - end. - -state__renew_race_list(RaceList, RaceListSize, - #state{races = Races} = State) -> - State#state{races = dialyzer_races:put_race_list(RaceList, RaceListSize, - Races)}. + fun_tab = FunTab, warning_mode = true}. state__renew_warnings(Warnings, State) -> State#state{warnings = Warnings}. --spec state__add_warning(raw_warning(), state()) -> state(). - -state__add_warning(Warn, #state{warnings = Warnings} = State) -> - State#state{warnings = [Warn|Warnings]}. - state__add_warning(State, Tag, Tree, Msg) -> state__add_warning(State, Tag, Tree, Msg, false). @@ -3190,15 +2971,6 @@ state__set_curr_fun(undefined, State) -> state__set_curr_fun(FunLbl, State) -> State#state{curr_fun = find_function(FunLbl, State)}. --spec state__find_function(mfa_or_funlbl(), state()) -> mfa_or_funlbl(). - -state__find_function(FunLbl, State) -> - find_function(FunLbl, State). - -state__get_race_warnings(#state{races = Races} = State) -> - {Races1, State1} = dialyzer_races:get_race_warnings(Races, State), - State1#state{races = Races1}. - state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, callgraph = Callgraph, plt = Plt, reachable_funs = ReachableFuns} = State) -> @@ -3526,56 +3298,6 @@ forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) -> State#state{work = NewWork, fun_tab = NewFunTab} end. --spec state__cleanup(state()) -> state(). - -state__cleanup(#state{callgraph = Callgraph, - races = Races, - records = Records}) -> - #state{callgraph = dialyzer_callgraph:cleanup(Callgraph), - races = dialyzer_races:cleanup(Races), - records = Records}. - --spec state__duplicate(state()) -> state(). - -state__duplicate(#state{callgraph = Callgraph} = State) -> - State#state{callgraph = dialyzer_callgraph:duplicate(Callgraph)}. - --spec dispose_state(state()) -> ok. - -dispose_state(#state{callgraph = Callgraph}) -> - dialyzer_callgraph:dispose_race_server(Callgraph). - --spec state__get_callgraph(state()) -> dialyzer_callgraph:callgraph(). - -state__get_callgraph(#state{callgraph = Callgraph}) -> - Callgraph. - --spec state__get_races(state()) -> dialyzer_races:races(). - -state__get_races(#state{races = Races}) -> - Races. - --spec state__get_records(state()) -> types(). - -state__get_records(#state{records = Records}) -> - Records. - --spec state__put_callgraph(dialyzer_callgraph:callgraph(), state()) -> - state(). - -state__put_callgraph(Callgraph, State) -> - State#state{callgraph = Callgraph}. - --spec state__put_races(dialyzer_races:races(), state()) -> state(). - -state__put_races(Races, State) -> - State#state{races = Races}. - --spec state__records_only(state()) -> state(). - -state__records_only(#state{records = Records}) -> - #state{records = Records}. - -spec state__translate_file(file:filename(), state()) -> file:filename(). state__translate_file(FakeFile, State) -> @@ -3584,51 +3306,12 @@ state__translate_file(FakeFile, State) -> %%% =========================================================================== %%% -%%% Races -%%% -%%% =========================================================================== - -is_race_analysis_enabled(#state{races = Races, callgraph = Callgraph}) -> - RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), - RaceAnalysis = dialyzer_races:get_race_analysis(Races), - RaceDetection andalso RaceAnalysis. - -get_race_list_and_size(#state{races = Races}) -> - dialyzer_races:get_race_list_and_size(Races). - -renew_race_code(#state{races = Races, callgraph = Callgraph, - warning_mode = WarningMode} = State) -> - case WarningMode of - true -> State; - false -> - NewCallgraph = dialyzer_callgraph:renew_race_code(Races, Callgraph), - State#state{callgraph = NewCallgraph} - end. - -renew_race_public_tables([Var], #state{races = Races, callgraph = Callgraph, - warning_mode = WarningMode} = State) -> - case WarningMode of - true -> State; - false -> - Table = dialyzer_races:get_new_table(Races), - case Table of - no_t -> State; - _Other -> - VarLabel = get_label(Var), - NewCallgraph = - dialyzer_callgraph:renew_race_public_tables(VarLabel, Callgraph), - State#state{callgraph = NewCallgraph} - end - end. - -%%% =========================================================================== -%%% %%% Worklist %%% %%% =========================================================================== init_work(List) -> - {List, [], sets:from_list(List)}. + {List, [], sets:from_list(List, [{version, 2}])}. get_work({[], [], _Set}) -> none; |