aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src/dialyzer_dataflow.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src/dialyzer_dataflow.erl')
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl181
1 files changed, 113 insertions, 68 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 178321ea18..7fb309497a 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -47,7 +47,7 @@
t_cons/0, t_cons/2, t_cons_hd/1, t_cons_tl/1, t_contains_opaque/1,
t_find_opaque_mismatch/2, t_float/0, t_from_range/2, t_from_term/1,
t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1,
- t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3,
+ t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3, t_inf_lists_masked/3,
t_integer/0, t_integers/1,
t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_boolean/1, t_is_equal/2,
t_is_integer/1, t_is_nil/1, t_is_none/1, t_is_none_or_unit/1,
@@ -93,11 +93,13 @@
tree_map :: dict(),
warning_mode = false :: boolean(),
warnings = [] :: [dial_warning()],
- work :: {[_], [_], set()}}).
+ work :: {[_], [_], set()},
+ module :: module(),
+ behaviour_api_info = [] :: [{atom(),[_]}]}).
%% Exported Types
--type state() :: #state{}.
+-opaque state() :: #state{}.
%%--------------------------------------------------------------------
@@ -263,10 +265,15 @@ analyze_module(Tree, Plt, Callgraph) ->
analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) ->
debug_pp(Tree, false),
Module = cerl:atom_val(cerl:module_name(Tree)),
+ RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph),
+ BehaviourTranslations =
+ case RaceDetection of
+ true -> dialyzer_behaviours:translatable_behaviours(Tree);
+ false -> []
+ end,
TopFun = cerl:ann_c_fun([{label, top}], [], Tree),
- State =
- state__new(dialyzer_callgraph:race_code_new(Callgraph),
- TopFun, Plt, Module, Records),
+ State = state__new(dialyzer_callgraph:race_code_new(Callgraph),
+ TopFun, Plt, Module, Records, BehaviourTranslations),
State1 = state__race_analysis(not GetWarnings, State),
State2 = analyze_loop(State1),
RaceCode = dialyzer_callgraph:get_race_code(Callgraph),
@@ -277,7 +284,18 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) ->
State3 = state__set_warning_mode(State2),
State4 = analyze_loop(State3),
State5 = state__restore_race_code(RaceCode, State4),
- dialyzer_races:race(State5);
+
+ %% EXPERIMENTAL: Turn all behaviour API calls into calls to the
+ %% respective callback module's functions.
+
+ case BehaviourTranslations of
+ [] -> dialyzer_races:race(State5);
+ Behaviours ->
+ TranslatedCallgraph =
+ dialyzer_behaviours:translate_callgraph(Behaviours, Module,
+ State5#state.callgraph),
+ dialyzer_races:race(State5#state{callgraph = TranslatedCallgraph})
+ end;
false ->
state__restore_race_code(
dict:merge(fun (_K, V1, _V2) -> V1 end,
@@ -567,6 +585,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
{M, F, A} = Fun,
case erl_bif_types:is_known(M, F, A) of
true ->
+ IsBIF = true,
BArgs = erl_bif_types:arg_types(M, F, A),
BRange =
fun(FunArgs) ->
@@ -585,9 +604,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
erl_bif_types:type(M, F, A, NewFunArgs)
end,
{BArgs, BRange};
- false -> GenSig
+ false -> IsBIF = false, GenSig
end;
- local -> GenSig
+ local -> IsBIF = false, GenSig
end,
{SigArgs, SigRange} =
%% if there is hard-coded or contract information with opaque types,
@@ -601,18 +620,33 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
none -> {AnyArgs, t_any()}
end
end,
- NewArgsSig = t_inf_lists(SigArgs, ArgTypes),
- NewArgsContract = t_inf_lists(CArgs, ArgTypes),
- NewArgsBif = t_inf_lists(BifArgs, ArgTypes),
- NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract),
- NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif),
+ ArgModeMask = [case lists:member(Arg, Opaques) of
+ true -> opaque;
+ false -> structured
+ end || Arg <- ArgTypes],
+ NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask),
+ NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask),
+ NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask),
+ NewArgTypes0 = t_inf_lists_masked(NewArgsSig, NewArgsContract, ArgModeMask),
+ NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask),
BifRet = BifRange(NewArgTypes),
- ContrRet = CRange(NewArgTypes),
- Mode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
+ {TmpArgTypes, TmpArgsContract} =
+ case (TypeOfApply == remote) andalso (not IsBIF) of
+ true ->
+ List1 = lists:zip(CArgs, NewArgTypes),
+ List2 = lists:zip(CArgs, NewArgsContract),
+ {[erl_types:t_unopaque_on_mismatch(T1, T2, Opaques)
+ || {T1, T2} <- List1],
+ [erl_types:t_unopaque_on_mismatch(T1, T2, Opaques)
+ || {T1, T2} <- List2]};
+ false -> {NewArgTypes, NewArgsContract}
+ end,
+ ContrRet = CRange(TmpArgTypes),
+ RetMode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
true -> opaque;
false -> structured
end,
- RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, Mode), SigRange, Mode),
+ RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode),
?debug("--------------------------------------------------------\n", []),
?debug("Fun: ~p\n", [Fun]),
?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
@@ -623,7 +657,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]),
?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]),
- ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]),
+ ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]),
?debug("SigRet: ~s\n", [erl_types:t_to_string(SigRange)]),
State1 =
case dialyzer_callgraph:get_race_detection(Callgraph) andalso
@@ -632,8 +666,21 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
Ann = cerl:get_ann(Tree),
File = get_file(Ann),
Line = abs(get_line(Ann)),
- dialyzer_races:store_race_call(Fun, ArgTypes, Args, {File, Line},
- State);
+
+ %% EXPERIMENTAL: Turn a behaviour's API call into a call to the
+ %% respective callback module's function.
+
+ Module = State#state.module,
+ BehApiInfo = State#state.behaviour_api_info,
+ {RealFun, RealArgTypes, RealArgs} =
+ case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes,
+ Args, Module,
+ BehApiInfo) of
+ plain_call -> {Fun, ArgTypes, Args};
+ BehaviourAPI -> BehaviourAPI
+ end,
+ dialyzer_races:store_race_call(RealFun, RealArgTypes, RealArgs,
+ {File, Line}, State);
false -> State
end,
FailedConj = any_none([RetWithoutLocal|NewArgTypes]),
@@ -643,7 +690,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
case FailedConj andalso not (IsFailBif orelse IsFailSig) of
true ->
FailedSig = any_none(NewArgsSig),
- FailedContract = any_none([CRange(NewArgsContract)|NewArgsContract]),
+ FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]),
FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
InfSig = t_inf(t_fun(SigArgs, SigRange),
t_fun(BifArgs, BifRange(BifArgs))),
@@ -786,8 +833,11 @@ expected_arg_triples(ArgNs, ArgTypes, State) ->
add_bif_warnings({erlang, Op, 2}, [T1, T2] = Ts, Tree, State)
when Op =:= '=:='; Op =:= '==' ->
+ Type1 = erl_types:t_unopaque(T1, State#state.opaques),
+ Type2 = erl_types:t_unopaque(T2, State#state.opaques),
Inf = t_inf(T1, T2),
- case t_is_none(Inf) andalso (not any_none(Ts))
+ Inf1 = t_inf(Type1, Type2),
+ case t_is_none(Inf) andalso t_is_none(Inf1) andalso(not any_none(Ts))
andalso (not is_int_float_eq_comp(T1, Op, T2)) of
true ->
Args = case erl_types:t_is_opaque(T1) of
@@ -905,9 +955,9 @@ handle_call(Tree, Map, State) ->
Args = cerl:call_args(Tree),
MFAList = [M, F|Args],
{State1, Map1, [MType0, FType0|As]} = traverse_list(MFAList, Map, State),
- %% Module and function names should be treated as *atoms* even if
- %% they happen to be identical to an atom which is also involved in
- %% the definition of an opaque data type
+ %% Module and function names should be treated as *structured terms*
+ %% even if they happen to be identical to an atom (or tuple) which
+ %% is also involved in the definition of an opaque data type.
MType = t_inf(t_module(), t_unopaque(MType0)),
FType = t_inf(t_atom(), t_unopaque(FType0)),
Map2 = enter_type_lists([M, F], [MType, FType], Map1),
@@ -936,13 +986,18 @@ handle_call(Tree, Map, State) ->
end,
{State2, Map2, t_none()};
false ->
- %% XXX: Consider doing this for all combinations of MF
- case {t_atom_vals(MType), t_atom_vals(FType)} of
- {[MAtom], [FAtom]} ->
- FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)},
- State1)}],
- handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1);
- {_MAtoms, _FAtoms} ->
+ case t_is_atom(MType) of
+ true ->
+ %% XXX: Consider doing this for all combinations of MF
+ case {t_atom_vals(MType), t_atom_vals(FType)} of
+ {[MAtom], [FAtom]} ->
+ FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)},
+ State1)}],
+ handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1);
+ {_MAtoms, _FAtoms} ->
+ {State1, Map2, t_any()}
+ end;
+ false ->
{State1, Map2, t_any()}
end
end.
@@ -1481,10 +1536,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
Cons = t_inf(Type, t_cons()),
case t_is_none(Cons) of
true ->
- case t_find_opaque_mismatch(t_cons(), Type) of
- {ok, T1, T2} -> bind_error([Pat], T1, T2, opaque);
- error -> bind_error([Pat], Type, t_none(), bind)
- end;
+ bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev);
false ->
{Map1, [HdType, TlType]} =
bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)],
@@ -1501,18 +1553,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end,
case t_is_none(t_inf(LiteralOrOpaque, Type)) of
true ->
- case t_find_opaque_mismatch(Literal, Type) of
- {ok, T1, T2} ->
- case lists:member(T2, State#state.opaques) of
- true ->
- NewType = erl_types:t_struct_from_opaque(Type, T2),
- {Map1, _} =
- bind_pat_vars([Pat], [NewType], [], Map, State, Rev),
- {Map1, T2};
- false -> bind_error([Pat], T1, T2, opaque)
- end;
- error -> bind_error([Pat], Type, t_none(), bind)
- end;
+ bind_opaque_pats(Literal, Type, Pat, Map, State, Rev);
false -> {Map, LiteralOrOpaque}
end;
tuple ->
@@ -1534,18 +1575,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
Tuple = t_inf(Prototype, Type),
case t_is_none(Tuple) of
true ->
- case t_find_opaque_mismatch(Prototype, Type) of
- {ok, T1, T2} ->
- case lists:member(T2, State#state.opaques) of
- true ->
- NewType = erl_types:t_struct_from_opaque(Type, T2),
- {Map1, _} =
- bind_pat_vars([Pat], [NewType], [], Map, State, Rev),
- {Map1, T2};
- false -> bind_error([Pat], T1, T2, opaque)
- end;
- error -> bind_error([Pat], Type, t_none(), bind)
- end;
+ bind_opaque_pats(Prototype, Type, Pat, Map, State, Rev);
false ->
SubTuples = t_tuple_subtypes(Tuple),
%% Need to call the top function to get the try-catch wrapper
@@ -1689,6 +1719,20 @@ bind_bin_segs([], _BinType, Acc, Map, _State) ->
bind_error(Pats, Type, OpaqueType, Error) ->
throw({error, Error, Pats, Type, OpaqueType}).
+bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) ->
+ case t_find_opaque_mismatch(GenType, Type) of
+ {ok, T1, T2} ->
+ case lists:member(T2, State#state.opaques) of
+ true ->
+ NewType = erl_types:t_struct_from_opaque(Type, [T2]),
+ {Map1, _} =
+ bind_pat_vars([Pat], [NewType], [], Map, State, Rev),
+ {Map1, T2};
+ false -> bind_error([Pat], T1, T2, opaque)
+ end;
+ error -> bind_error([Pat], Type, t_none(), bind)
+ end.
+
%%----------------------------------------
%% Guards
%%
@@ -2296,7 +2340,7 @@ bind_guard_list([G|Gs], Map, Env, Eval, State, Acc) ->
bind_guard_list([], Map, _Env, _Eval, _State, Acc) ->
{Map, lists:reverse(Acc)}.
--spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], #state{}) ->
+-spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], state()) ->
no_return().
signal_guard_fail(Guard, ArgTypes, State) ->
@@ -2327,7 +2371,7 @@ 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.
--spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], #state{}) ->
+-spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], state()) ->
no_return().
signal_guard_fatal_fail(Guard, ArgTypes, State) ->
@@ -2680,7 +2724,7 @@ determine_mode(Type, Opaques) ->
%%%
%%% ===========================================================================
-state__new(Callgraph, Tree, Plt, Module, Records) ->
+state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) ->
TreeMap = build_tree_map(Tree),
Funs = dict:fetch_keys(TreeMap),
FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt),
@@ -2690,7 +2734,8 @@ state__new(Callgraph, Tree, Plt, Module, Records) ->
erl_types:t_opaque_from_records(Records),
#state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques,
plt = Plt, races = dialyzer_races:new(), records = Records,
- warning_mode = false, warnings = [], work = Work, tree_map = TreeMap}.
+ warning_mode = false, warnings = [], work = Work, tree_map = TreeMap,
+ module = Module, behaviour_api_info = BehaviourTranslations}.
state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) ->
Fun = get_label(Fun0),
@@ -3197,7 +3242,7 @@ get_file([_|Tail]) -> get_file(Tail).
is_compiler_generated(Ann) ->
lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1).
--spec format_args([term()], [erl_types:erl_type()], #state{}) ->
+-spec format_args([term()], [erl_types:erl_type()], state()) ->
nonempty_string().
format_args([], [], _State) ->
@@ -3205,7 +3250,7 @@ format_args([], [], _State) ->
format_args(ArgList, TypeList, State) ->
"(" ++ format_args_1(ArgList, TypeList, State) ++ ")".
--spec format_args_1([term(),...], [erl_types:erl_type(),...], #state{}) ->
+-spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) ->
string().
format_args_1([Arg], [Type], State) ->
@@ -3235,12 +3280,12 @@ format_arg(Arg) ->
Default
end.
--spec format_type(erl_types:erl_type(), #state{}) -> string().
+-spec format_type(erl_types:erl_type(), state()) -> string().
format_type(Type, #state{records = R}) ->
t_to_string(Type, R).
--spec format_sig_args(erl_types:erl_type(), #state{}) -> string().
+-spec format_sig_args(erl_types:erl_type(), state()) -> string().
format_sig_args(Type, #state{records = R}) ->
SigArgs = t_fun_args(Type),