summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Gustavsson <bjorn@erlang.org>2019-11-25 10:02:46 +0100
committerBjörn Gustavsson <bjorn@erlang.org>2020-02-06 12:27:30 +0100
commit1b23945dd7b3f0c8e88692ddfe41de446230ac5d (patch)
treeb4611c08fb9adf5ba9a2c2c3c49641ad55b59782
parent9ab69adb8b58384019b20bb60cf81f4051bc2f9f (diff)
downloaderlang-1b23945dd7b3f0c8e88692ddfe41de446230ac5d.tar.gz
Teach Dialyzer to handle the refactored Core Erlang representation
Add support to Dialyzer to handle the primops and the new way of using letrecs.
-rw-r--r--lib/dialyzer/src/Makefile1
-rw-r--r--lib/dialyzer/src/dialyzer.app.src1
-rw-r--r--lib/dialyzer/src/dialyzer_clean_core.erl225
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl76
-rw-r--r--lib/dialyzer/src/dialyzer_dep.erl82
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl43
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl4
-rw-r--r--lib/hipe/cerl/cerl_closurean.erl4
8 files changed, 351 insertions, 85 deletions
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index bddd761705..1f5b308c7d 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -53,6 +53,7 @@ MODULES = \
dialyzer_callgraph \
dialyzer_cl \
dialyzer_cl_parse \
+ dialyzer_clean_core \
dialyzer_codeserver \
dialyzer_contracts \
dialyzer_dataflow \
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index e3a0fc967d..36f5d96ea6 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -28,6 +28,7 @@
dialyzer_callgraph,
dialyzer_cl,
dialyzer_cl_parse,
+ dialyzer_clean_core,
dialyzer_codeserver,
dialyzer_contracts,
dialyzer_coordinator,
diff --git a/lib/dialyzer/src/dialyzer_clean_core.erl b/lib/dialyzer/src/dialyzer_clean_core.erl
new file mode 100644
index 0000000000..d591ad3473
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_clean_core.erl
@@ -0,0 +1,225 @@
+%% -*- 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.
+
+-module(dialyzer_clean_core).
+-export([clean/1]).
+
+-spec clean(cerl:cerl()) -> cerl:cerl().
+
+clean(Tree) ->
+ case cerl:type(Tree) of
+ apply ->
+ Op = clean(cerl:apply_op(Tree)),
+ Args = clean_list(cerl:apply_args(Tree)),
+ cerl:update_c_apply(Tree, Op, Args);
+ binary ->
+ Segments = clean_list(cerl:binary_segments(Tree)),
+ cerl:update_c_binary(Tree, Segments);
+ bitstr ->
+ Val = clean(cerl:bitstr_val(Tree)),
+ Size = clean(cerl:bitstr_size(Tree)),
+ Unit = cerl:bitstr_unit(Tree),
+ Type = cerl:bitstr_type(Tree),
+ Flags = cerl:bitstr_flags(Tree),
+ cerl:update_c_bitstr(Tree, Val, Size, Unit, Type, Flags);
+ 'case' ->
+ Arg = clean(cerl:case_arg(Tree)),
+ Clauses = clean_clauses(cerl:case_clauses(Tree)),
+ cerl:update_c_case(Tree, Arg, Clauses);
+ call ->
+ Args = clean_list(cerl:call_args(Tree)),
+ Module = clean(cerl:call_module(Tree)),
+ Name = clean(cerl:call_name(Tree)),
+ cerl:update_c_call(Tree, Module, Name, Args);
+ 'catch' ->
+ Body = clean(cerl:catch_body(Tree)),
+ cerl:update_c_catch(Tree, Body);
+ cons ->
+ Hd = clean(cerl:cons_hd(Tree)),
+ Tl = clean(cerl:cons_tl(Tree)),
+ cerl:update_c_cons_skel(Tree, Hd, Tl);
+ 'fun' ->
+ Body = clean(cerl:fun_body(Tree)),
+ Vars = cerl:fun_vars(Tree),
+ cerl:update_c_fun(Tree, Vars, Body);
+ 'let' ->
+ Arg = clean(cerl:let_arg(Tree)),
+ Body = clean(cerl:let_body(Tree)),
+ Vars = cerl:let_vars(Tree),
+ cerl:update_c_let(Tree, Vars, Arg, Body);
+ letrec ->
+ clean_letrec(Tree);
+ literal ->
+ Tree;
+ module ->
+ Defs = clean_defs(cerl:module_defs(Tree)),
+ Name = cerl:module_name(Tree),
+ Exports = cerl:module_exports(Tree),
+ Attrs = cerl:module_attrs(Tree),
+ cerl:update_c_module(Tree, Name, Exports, Attrs, Defs);
+ primop ->
+ Args = clean_list(cerl:primop_args(Tree)),
+ Name = cerl:primop_name(Tree),
+ cerl:update_c_primop(Tree, Name, Args);
+ 'receive' ->
+ Clauses = clean_clauses(cerl:receive_clauses(Tree)),
+ Timeout = clean(cerl:receive_timeout(Tree)),
+ Action = clean(cerl:receive_action(Tree)),
+ cerl:update_c_receive(Tree, Clauses, Timeout, Action);
+ seq ->
+ Arg = clean(cerl:seq_arg(Tree)),
+ Body = clean(cerl:seq_body(Tree)),
+ cerl:update_c_seq(Tree, Arg, Body);
+ 'try' ->
+ Arg = clean(cerl:try_arg(Tree)),
+ Body = clean(cerl:try_body(Tree)),
+ Handler = clean(cerl:try_handler(Tree)),
+ Vs = cerl:try_vars(Tree),
+ Evs = cerl:try_evars(Tree),
+ Try = cerl:update_c_try(Tree, Arg, Vs, Body, Evs, Handler),
+ Try;
+ tuple ->
+ Elements = clean_list(cerl:tuple_es(Tree)),
+ cerl:update_c_tuple_skel(Tree, Elements);
+ map ->
+ Arg = clean(cerl:map_arg(Tree)),
+ Entries = clean_map_pairs(cerl:map_es(Tree)),
+ cerl:update_c_map(Tree, Arg, Entries);
+ values ->
+ Elements = clean_list(cerl:values_es(Tree)),
+ cerl:update_c_values(Tree, Elements);
+ var ->
+ Tree
+ end.
+
+clean_letrec(Tree) ->
+ case lists:member(letrec_goto, cerl:get_ann(Tree)) of
+ true ->
+ %% This is a restricted form of letrec used to allow rewriting
+ %% pattern matching without duplicating code. When a letrec is
+ %% used in this way, Dialyzer will not be able to infer much
+ %% type information, so we will need to eliminate the letrec.
+ [{_Name, Fun}] = cerl:letrec_defs(Tree),
+ FunBody = cerl:fun_body(Fun),
+ FunBody1 = clean(FunBody),
+ Body = clean(cerl:letrec_body(Tree)),
+ case dialyzer_ignore(Body) of
+ true ->
+ %% The body of the letrec directly transfer controls to
+ %% defined function in the letrec. We only need to keep
+ %% the body of that function. (This is is the code for
+ %% a receive construct.)
+ FunBody1;
+ false ->
+ %% The body is non-trivial. Here is an example:
+ %%
+ %% letrec 'more_matching'/0 =
+ %% fun () ->
+ %% case CaseExpr of . . . end
+ %% end
+ %% in case CaseExpr of
+ %% <<..., Tail>> ->
+ %% case Tail of
+ %% <<...>> -> . . .
+ %% _ -> apply 'more_matching'/0()
+ %% end
+ %% _ -> apply 'more_matching'/0()
+ %% end
+ %%
+ %% The clauses that invoke `apply` are marked with
+ %% a `dialyzer_ignore` annotation to indicate that
+ %% Dialyzer should ignore them.
+ %%
+ %% The example is translated like this:
+ %%
+ %% case primop:dialyzer_unknown() of
+ %% 'a' ->
+ %% case Var of
+ %% <<..., Tail>> ->
+ %% case Tail of
+ %% <<...>> -> . . .
+ %% end
+ %% end
+ %% 'b' ->
+ %% %% Body of more_matching/0.
+ %% case Var of . . . end
+ %% end
+ %%
+ PrimopUnknown = cerl:c_primop(cerl:abstract(dialyzer_unknown), []),
+ Clauses = [cerl:c_clause([cerl:abstract(a)], Body),
+ cerl:c_clause([cerl:abstract(b)], FunBody1)],
+ cerl:c_case(PrimopUnknown, Clauses)
+ end;
+ false ->
+ %% This is a plain letrec. (Originating from a list or binary comprehension.)
+ Defs = clean_defs(cerl:letrec_defs(Tree)),
+ Body = clean(cerl:letrec_body(Tree)),
+ cerl:update_c_letrec(Tree, Defs, Body)
+ end.
+
+clean_defs(Defs) ->
+ [{Name, clean(Fun)} || {Name, Fun} <- Defs].
+
+clean_clauses([Clause|Tail]) ->
+ case clean_clause(Clause) of
+ ignore ->
+ %% The clause is either annotated with `dialyzer_ignore` or its
+ %% body is primop that raises an exception.
+ clean_clauses(Tail);
+ Clause1 ->
+ Tail1 = clean_clauses(Tail),
+ [Clause1|Tail1]
+ end;
+clean_clauses([]) ->
+ [].
+
+clean_clause(Clause) ->
+ Body = cerl:clause_body(Clause),
+ case dialyzer_ignore(Clause) orelse is_raising_body(Body) of
+ true ->
+ ignore;
+ false ->
+ G = clean(cerl:clause_guard(Clause)),
+ Body1 = clean(Body),
+ Pats = cerl:clause_pats(Clause),
+ cerl:update_c_clause(Clause, Pats, G, Body1)
+ end.
+
+is_raising_body(Body) ->
+ case cerl:type(Body) of
+ primop ->
+ case cerl:atom_val(cerl:primop_name(Body)) of
+ match_fail -> true;
+ raise -> true;
+ _ -> false
+ end;
+ _ ->
+ false
+ end.
+
+clean_list(Trees) ->
+ [clean(Tree) || Tree <- Trees].
+
+clean_map_pairs([Pair|Pairs]) ->
+ Key = clean(cerl:map_pair_key(Pair)),
+ Val = clean(cerl:map_pair_val(Pair)),
+ Pairs1 = clean_map_pairs(Pairs),
+ Op = cerl:map_pair_op(Pair),
+ Pair1 = cerl:update_c_map_pair(Pair, Op, Key, Val),
+ [Pair1|Pairs1];
+clean_map_pairs([]) ->
+ [].
+
+dialyzer_ignore(Tree) ->
+ lists:member(dialyzer_ignore, cerl:get_ann(Tree)).
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 55c77814f8..cff3981393 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -298,15 +298,36 @@ traverse(Tree, Map, State) ->
module ->
handle_module(Tree, Map, State);
primop ->
- Type =
- case cerl:atom_val(cerl:primop_name(Tree)) of
- match_fail -> t_none();
- raise -> t_none();
- bs_init_writable -> t_from_term(<<>>);
- build_stacktrace -> erl_bif_types:type(erlang, build_stacktrace, 0);
- Other -> erlang:error({'Unsupported primop', Other})
- end,
- {State, Map, Type};
+ 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);
seq ->
@@ -967,7 +988,7 @@ handle_call(Tree, Map, State) ->
handle_case(Tree, Map, State) ->
Arg = cerl:case_arg(Tree),
- Clauses = filter_match_fail(cerl:case_clauses(Tree)),
+ Clauses = cerl:case_clauses(Tree),
{State1, Map1, ArgType} = SMA = traverse(Arg, Map, State),
case t_is_none_or_unit(ArgType) of
true -> SMA;
@@ -1084,7 +1105,7 @@ handle_module(Tree, Map, State) ->
%%----------------------------------------
handle_receive(Tree, Map, State) ->
- Clauses = filter_match_fail(cerl:receive_clauses(Tree)),
+ Clauses = cerl:receive_clauses(Tree),
Timeout = cerl:receive_timeout(Tree),
State1 =
case is_race_analysis_enabled(State) of
@@ -3019,24 +3040,6 @@ is_lc_simple_list(Tree, TreeType, State) ->
andalso t_is_list(TreeType)
andalso t_is_simple(t_list_elements(TreeType, Opaques), State).
-filter_match_fail([Clause] = Cls) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> Cls
- end;
- _ -> Cls
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
%%% ===========================================================================
%%%
%%% The State.
@@ -3819,7 +3822,20 @@ find_terminals(Tree) ->
%% We cannot make assumptions. Say that both are true.
{true, true}
end;
- 'case' -> find_terminals_list(cerl:case_clauses(Tree));
+ 'case' ->
+ case cerl:case_clauses(Tree) of
+ [] ->
+ case lists:member(receive_timeout, cerl:get_ann(Tree)) of
+ true ->
+ %% Handle a never ending receive without any
+ %% clauses specially. (Not sure why.)
+ {false, true};
+ false ->
+ {false, false}
+ end;
+ [_|_] ->
+ find_terminals_list(cerl:case_clauses(Tree))
+ end;
'catch' -> find_terminals(cerl:catch_body(Tree));
clause -> find_terminals(cerl:clause_body(Tree));
cons -> {false, true};
diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl
index 36cdc0876c..c8e9256bc8 100644
--- a/lib/dialyzer/src/dialyzer_dep.erl
+++ b/lib/dialyzer/src/dialyzer_dep.erl
@@ -87,7 +87,8 @@ traverse(Tree, Out, State, CurrentFun) ->
true ->
%% Op is a variable and should not be marked as escaping
%% based on its use.
- OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
+ OpLabel = cerl_trees:get_label(Op),
+ OpFuns = case map__lookup(OpLabel, Out) of
none -> output(none);
{value, OF} -> OF
end,
@@ -96,7 +97,13 @@ traverse(Tree, Out, State, CurrentFun) ->
State4 = state__add_deps(CurrentFun, OpFuns, State3),
State5 = state__store_callsite(cerl_trees:get_label(Tree),
OpFuns, length(Args), State4),
- {output(set__singleton(external)), State5}
+ case state__get_rvals(OpLabel, State5) of
+ 1 ->
+ {output(set__singleton(external)), State5};
+ NumRvals ->
+ List = lists:duplicate(NumRvals, output(set__singleton(external))),
+ {output(List), State5}
+ end
end;
binary ->
{output(none), State};
@@ -137,9 +144,12 @@ traverse(Tree, Out, State, CurrentFun) ->
Vars = cerl:let_vars(Tree),
Arg = cerl:let_arg(Tree),
Body = cerl:let_body(Tree),
- {ArgFuns, State1} = traverse(Arg, Out, State, CurrentFun),
+ OldNumRvals = state__num_rvals(State),
+ State1 = state__store_num_rvals(length(Vars), State),
+ {ArgFuns, State2} = traverse(Arg, Out, State1, CurrentFun),
Out1 = bind_list(Vars, ArgFuns, Out),
- traverse(Body, Out1, State1, CurrentFun);
+ State3 = state__store_num_rvals(OldNumRvals, State2),
+ traverse(Body, Out1, State3, CurrentFun);
letrec ->
Defs = cerl:letrec_defs(Tree),
Body = cerl:letrec_body(Tree),
@@ -147,14 +157,15 @@ traverse(Tree, Out, State, CurrentFun) ->
state__add_letrecs(cerl_trees:get_label(Var), cerl_trees:get_label(Fun), Acc)
end, State, Defs),
Out1 = bind_defs(Defs, Out),
- State2 = traverse_defs(Defs, Out1, State1, CurrentFun),
+ NumRvals = state__num_rvals(State1),
+ State2 = traverse_defs(Defs, Out1, State1, CurrentFun, NumRvals),
traverse(Body, Out1, State2, CurrentFun);
literal ->
{output(none), State};
module ->
Defs = cerl:module_defs(Tree),
Out1 = bind_defs(Defs, Out),
- State1 = traverse_defs(Defs, Out1, State, CurrentFun),
+ State1 = traverse_defs(Defs, Out1, State, CurrentFun, 1),
{output(none), State1};
primop ->
Args = cerl:primop_args(Tree),
@@ -223,14 +234,15 @@ traverse_list([Tree|Left], Out, State, CurrentFun, Acc) ->
traverse_list([], _Out, State, _CurrentFun, Acc) ->
{output(lists:reverse(Acc)), State}.
-traverse_defs([{_, Fun}|Left], Out, State, CurrentFun) ->
- {_, State1} = traverse(Fun, Out, State, CurrentFun),
- traverse_defs(Left, Out, State1, CurrentFun);
-traverse_defs([], _Out, State, _CurrentFun) ->
+traverse_defs([{_, Fun}|Left], Out, State, CurrentFun, NumRvals) ->
+ State1 = state__store_num_rvals(NumRvals, State),
+ {_, State2} = traverse(Fun, Out, State1, CurrentFun),
+ traverse_defs(Left, Out, State2, CurrentFun, NumRvals);
+traverse_defs([], _Out, State, _CurrentFun, _NumRvals) ->
State.
traverse_clauses(Clauses, ArgFuns, Out, State, CurrentFun) ->
- case filter_match_fail(Clauses) of
+ case Clauses of
[] ->
%% Can happen for example with receives used as timouts.
{output(none), State};
@@ -249,24 +261,6 @@ traverse_clauses([Clause|Left], ArgFuns, Out, State, CurrentFun, Acc) ->
traverse_clauses([], _ArgFuns, _Out, State, _CurrentFun, Acc) ->
{merge_outs(Acc), State}.
-filter_match_fail([Clause]) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> [Clause]
- end;
- _ -> [Clause]
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
remote_call(Tree, ArgFuns, State) ->
M = cerl:call_module(Tree),
F = cerl:call_name(Tree),
@@ -483,12 +477,16 @@ all_vars(Tree, AccIn) ->
%%
-type local_set() :: 'none' | #set{}.
+-type rvals() :: #{label() => non_neg_integer()}.
-record(state, {deps :: deps(),
esc :: local_set(),
calls :: calls(),
arities :: dict:dict(label() | 'top', arity()),
- letrecs :: letrecs()}).
+ letrecs :: letrecs(),
+ num_rvals = 1 :: non_neg_integer(),
+ rvals = #{} :: rvals()
+ }).
state__new(Tree) ->
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
@@ -526,8 +524,11 @@ state__add_deps(From, #output{type = single, content = To},
%% io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
State#state{deps = map__add(From, To, Map)}.
-state__add_letrecs(Var, Fun, #state{letrecs = Map} = State) ->
- State#state{letrecs = map__store(Var, Fun, Map)}.
+state__add_letrecs(Var, Fun, #state{letrecs = Map,
+ num_rvals = NumRvals,
+ rvals = Rvals} = State) ->
+ State#state{letrecs = map__store(Var, Fun, Map),
+ rvals = Rvals#{Var => NumRvals}}.
state__deps(#state{deps = Deps}) ->
Deps.
@@ -539,6 +540,10 @@ state__add_esc(#output{content = none}, State) ->
State;
state__add_esc(#output{type = single, content = Set},
#state{esc = Esc} = State) ->
+ State#state{esc = set__union(Set, Esc)};
+state__add_esc(#output{type = list, content = [H|T]},
+ #state{esc = Esc} = State) ->
+ #output{type = single, content = Set} = merge_outs(T, H),
State#state{esc = set__union(Set, Esc)}.
state__esc(#state{esc = Esc}) ->
@@ -559,6 +564,19 @@ state__store_callsite(From, To, CallArity,
state__calls(#state{calls = Calls}) ->
Calls.
+state__store_num_rvals(NumRval, State) ->
+ State#state{num_rvals = NumRval}.
+
+state__num_rvals(#state{num_rvals = NumRvals}) ->
+ NumRvals.
+
+state__get_rvals(FunLabel, #state{rvals = Rvals}) ->
+ case Rvals of
+ #{FunLabel := NumRvals} -> NumRvals;
+ #{} -> 1
+ end.
+
+
%%------------------------------------------------------------
%% A test function. Not part of the intended interface.
%%
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index dede475f98..5f40f80ae7 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -296,7 +296,7 @@ traverse(Tree, DefinedVars, State) ->
{State4, mk_var(Tree)};
'case' ->
Arg = cerl:case_arg(Tree),
- Clauses = filter_match_fail(cerl:case_clauses(Tree)),
+ Clauses = cerl:case_clauses(Tree),
{State1, ArgVar} = traverse(Arg, DefinedVars, State),
handle_clauses(Clauses, mk_var(Tree), ArgVar, DefinedVars, State1);
call ->
@@ -423,10 +423,31 @@ traverse(Tree, DefinedVars, State) ->
Type = erl_bif_types:type(erlang, build_stacktrace, 0),
State1 = state__store_conj(V, sub, Type, State),
{State1, V};
+ dialyzer_unknown ->
+ %% See dialyzer_clean_core:clean_letrec/1.
+ {State, mk_var(Tree)};
+ recv_peek_message ->
+ {State1, Vars} = state__mk_vars(2, State),
+ {State1, t_product(Vars)};
+ recv_wait_timeout ->
+ [Timeout] = cerl:primop_args(Tree),
+ case cerl:is_c_atom(Timeout) andalso
+ cerl:atom_val(Timeout) =:= infinity of
+ true ->
+ {State, t_none()};
+ false ->
+ {State1, TimeoutVar} = traverse(Timeout, DefinedVars, State),
+ State2 = state__store_conj(TimeoutVar, sub, t_timeout(), State1),
+ {State2, mk_var(Tree)}
+ end;
+ remove_message ->
+ {State, t_any()};
+ timeout ->
+ {State, t_any()};
Other -> erlang:error({'Unsupported primop', Other})
end;
'receive' ->
- Clauses = filter_match_fail(cerl:receive_clauses(Tree)),
+ Clauses = cerl:receive_clauses(Tree),
Timeout = cerl:receive_timeout(Tree),
case (cerl:is_c_atom(Timeout) andalso
(cerl:atom_val(Timeout) =:= infinity)) of
@@ -829,24 +850,6 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
get_contract_return(C, ArgTypes) ->
dialyzer_contracts:get_contract_return(C, ArgTypes).
-filter_match_fail([Clause] = Cls) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> Cls
- end;
- _ -> Cls
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
%% If there is a significant number of clauses, we cannot apply the
%% list subtraction scheme since it causes the analysis to be too
%% slow. Typically, this only affects automatically generated files.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 245c099fef..f679f146cc 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -99,7 +99,7 @@ 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, _, Core} -> {ok, Core}
+ {ok, _, Core} -> {ok, dialyzer_clean_core:clean(Core)}
end.
-type get_core_from_beam_ret() :: {'ok', cerl:c_module()} | {'error', string()}.
@@ -116,7 +116,7 @@ get_core_from_beam(File, Opts) ->
{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};
+ {ok, dialyzer_clean_core:clean(Core)};
{error, _} ->
{error, " Could not get Core Erlang code for: " ++ File ++ "\n"}
end;
diff --git a/lib/hipe/cerl/cerl_closurean.erl b/lib/hipe/cerl/cerl_closurean.erl
index a2bd7fe0f0..583c5d624a 100644
--- a/lib/hipe/cerl/cerl_closurean.erl
+++ b/lib/hipe/cerl/cerl_closurean.erl
@@ -797,7 +797,8 @@ take_work({Queue0, Set0}) ->
-spec is_escape_op(atom(), arity()) -> boolean().
-is_escape_op(match_fail, 1) -> false;
+is_escape_op(match_fail, 1) -> false;
+is_escape_op(recv_wait_timeout, 1) -> false;
is_escape_op(F, A) when is_atom(F), is_integer(A) -> true.
-spec is_escape_op(atom(), atom(), arity()) -> boolean().
@@ -814,6 +815,7 @@ is_escape_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> true.
-spec is_literal_op(atom(), arity()) -> boolean().
+is_literal_op(recv_wait_timeout, 1) -> true;
is_literal_op(match_fail, 1) -> true;
is_literal_op(F, A) when is_atom(F), is_integer(A) -> false.