aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorBjörn Gustavsson <[email protected]>2019-08-13 06:38:24 +0200
committerBjörn Gustavsson <[email protected]>2019-08-14 07:10:17 +0200
commitc5e36feada9d0362108890f42c40dd2398b1b531 (patch)
tree802db75152357454c28ea42680ccfa8d45d0b355 /lib
parentd203e847548586f6ef80ac0e73a5f6f173efe1c6 (diff)
downloadotp-c5e36feada9d0362108890f42c40dd2398b1b531.tar.gz
otp-c5e36feada9d0362108890f42c40dd2398b1b531.tar.bz2
otp-c5e36feada9d0362108890f42c40dd2398b1b531.zip
Fix compiler crash when compiling some receive statements
The compiler would crash when compiling the following code: do(Acc) -> receive {Pid, abc} -> ok; {Pid, []} -> ok; {Pid, _Res} -> exit(_Res) end, do([Pid | Acc]). The last clause that always raises an exception would confuse the compiler so that it would think that the `receive` statement was at the end of the function and it would generate incorrect code for the `do/1` call following the `receive`. https://bugs.erlang.org/browse/ERL-1022
Diffstat (limited to 'lib')
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl62
-rw-r--r--lib/compiler/test/receive_SUITE.erl35
2 files changed, 79 insertions, 18 deletions
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 7ef604d444..7f816b9802 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -1463,25 +1463,51 @@ fix_receive([], _Defs, Blocks, Count) ->
{Blocks,Count}.
%% find_loop_exit([Label], Blocks) -> Label | none.
-%% Find the block to which control is transferred when the
-%% the receive loop is exited.
-
-find_loop_exit([L1,L2|_Ls], Blocks) ->
- Path1 = beam_ssa:rpo([L1], Blocks),
- Path2 = beam_ssa:rpo([L2], Blocks),
- find_loop_exit_1(Path1, cerl_sets:from_list(Path2));
-find_loop_exit(_, _) -> none.
-
-find_loop_exit_1([?BADARG_BLOCK | T], OtherPath) ->
- %% ?BADARG_BLOCK is a marker and not an actual block, so we can't consider
- %% it to be a common block even if both paths cross it.
- find_loop_exit_1(T, OtherPath);
-find_loop_exit_1([H|T], OtherPath) ->
- case cerl_sets:is_element(H, OtherPath) of
- true -> H;
- false -> find_loop_exit_1(T, OtherPath)
+%% Given the list of all blocks with the remove_message instructions
+%% for this receive, find the block to which control is transferred
+%% when the receive loop is exited (if any).
+
+find_loop_exit([_,_|_]=RmBlocks, Blocks) ->
+ %% We used to only analyze the path from two of the remove_message
+ %% blocks. That would fail to find a common block if one or both
+ %% of the blocks happened to raise an exception. To be sure that
+ %% we always find a common block if there is one (shared by at
+ %% least two clauses), we must analyze the path from all
+ %% remove_message blocks.
+ {Dominators,_} = beam_ssa:dominators(Blocks),
+ RmSet = cerl_sets:from_list(RmBlocks),
+ Rpo = beam_ssa:rpo(RmBlocks, Blocks),
+ find_loop_exit_1(Rpo, RmSet, Dominators);
+find_loop_exit(_, _) ->
+ %% There is (at most) a single clause. There is no common
+ %% loop exit block.
+ none.
+
+find_loop_exit_1([?BADARG_BLOCK|Ls], RmSet, Dominators) ->
+ %% ?BADARG_BLOCK is a marker and not an actual block, so it is not
+ %% the block we are looking for.
+ find_loop_exit_1(Ls, RmSet, Dominators);
+find_loop_exit_1([L|Ls], RmSet, Dominators) ->
+ DomBy = map_get(L, Dominators),
+ case any(fun(E) -> cerl_sets:is_element(E, RmSet) end, DomBy) of
+ true ->
+ %% This block is dominated by one of the remove_message blocks,
+ %% which means that the block is part of only one clause.
+ %% It is not the block we are looking for.
+ find_loop_exit_1(Ls, RmSet, Dominators);
+ false ->
+ %% This block is the first block that is not dominated by
+ %% any of the blocks with remove_message instructions,
+ %% which means that at least two of the receive clauses
+ %% will ultimately transfer control to it. It is the block
+ %% we are looking for.
+ L
end;
-find_loop_exit_1([], _) -> none.
+find_loop_exit_1([], _, _) ->
+ %% None of clauses transfers control to a common block after the receive
+ %% statement. That means that the receive statement is a the end of a
+ %% function (or that all clauses raise exceptions).
+ none.
%% find_rm_blocks(StartLabel, Blocks) -> [Label].
%% Find all blocks that start with remove_message within the receive
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 752491f0f8..8cd864c59e 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -431,6 +431,20 @@ elusive_common_exit(_Config) ->
self() ! {1, a},
self() ! {2, b},
{[z], [{2,b},{1,a}]} = elusive_loop([x,y,z], 2, []),
+
+ CodeServer = whereis(code_server),
+ Self = self(),
+ Self ! {Self, abc},
+ Self ! {CodeServer, []},
+ Self ! {Self, other},
+ try elusive2([]) of
+ Unexpected ->
+ ct:fail("Expected an exception; got ~p\n", [Unexpected])
+ catch
+ throw:[other, CodeServer, Self] ->
+ ok
+ end,
+
ok.
elusive_loop(List, 0, Results) ->
@@ -449,4 +463,25 @@ elusive_loop(List, ToReceive, Results) ->
%% that it would not insert all necessary copy instructions.
elusive_loop(RemList, ToReceive-1, [Result | Results]).
+
+elusive2(Acc) ->
+ receive
+ {Pid, abc} ->
+ ok;
+ {Pid, []} ->
+ ok;
+ {Pid, Res} ->
+ %% beam_ssa_pre_codegen:find_loop_exit/2 attempts to find
+ %% the first block of the common code after the receive
+ %% statement. It used to only look at the two last clauses
+ %% of the receive. In this function, the last two clauses
+ %% don't have any common block, so it would be assumed
+ %% that there was no common block for any of the
+ %% clauses. That would mean that copy instructions would
+ %% not be inserted as needed.
+ throw([Res | Acc])
+ end,
+ %% Common code.
+ elusive2([Pid | Acc]).
+
id(I) -> I.