1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2016-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(beam_jump_SUITE).
21
22-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
23	 init_per_group/2,end_per_group/2,
24	 undefined_label/1,ambiguous_catch_try_state/1,
25         unsafe_move_elimination/1,build_tuple/1,
26         coverage/1,call_sharing/1,undecided_allocation/1]).
27
28suite() ->
29    [{ct_hooks,[ts_install_cth]}].
30
31all() ->
32    [{group,p}].
33
34groups() ->
35    [{p,[parallel],
36      [undefined_label,
37       ambiguous_catch_try_state,
38       unsafe_move_elimination,
39       build_tuple,
40       coverage,
41       call_sharing,
42       undecided_allocation
43      ]}].
44
45init_per_suite(Config) ->
46    test_lib:recompile(?MODULE),
47    Config.
48
49end_per_suite(_Config) ->
50    ok.
51
52init_per_group(_GroupName, Config) ->
53    Config.
54
55end_per_group(_GroupName, Config) ->
56    Config.
57
58undefined_label(_Config) ->
59    {'EXIT',{function_clause,_}} = (catch flights(0, [], [])),
60    ok.
61
62%% Would lose a label when compiled with no_copt.
63
64flights(0, [], []) when [], 0; 0.0, [], false ->
65    clark;
66flights(_, Reproduction, introduction) when false, Reproduction ->
67    responsible.
68
69%% [ERL-209] beam_jump would share 'catch' blocks, causing an
70%% ambiguous_catch_try_state error in beam_validator.
71
72ambiguous_catch_try_state(Config) ->
73    {{'EXIT',{{case_clause,song},_}},{'EXIT',{{case_clause,song},_}}} =
74	checks(42),
75
76    {'EXIT',{{try_clause,42},_}} = (catch unsafe_sharing()),
77
78    {'EXIT',{{badmatch,b},_}} = (catch ambiguous_catch_try_state_1(<<>>)),
79    {'EXIT',{{badmatch,b},_}} = (catch ambiguous_catch_try_state_1(Config)),
80
81    {'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_2()),
82    {'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_3()),
83
84    ok.
85
86river() -> song.
87
88checks(Wanted) ->
89    %% Must be one line to cause the unsafe optimization.
90    {catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}.
91
92%% Must be one line to cause the unsafe optimization. Would cause beam_validator to reject the function.
93unsafe_sharing() -> try try id(42) catch parent:215 -> []; education:17 -> try 12 catch _:_ -> a end /= if false -> fy end end of [] -> if false -> a end catch _:_ -> name end.
94
95unsafe_move_elimination(_Config) ->
96    {{left,right,false},false} = unsafe_move_elimination_1(left, right, false),
97    {{false,right,false},false} = unsafe_move_elimination_1(false, right, true),
98    {{true,right,right},right} = unsafe_move_elimination_1(true, right, true),
99    [ok = unsafe_move_elimination_2(I) || I <- lists:seq(0,16)],
100    ok.
101
102unsafe_move_elimination_1(Left, Right, Simple0) ->
103    id(1),
104
105    %% The move at label 29 would be removed by beam_jump, which is unsafe because
106    %% the two select_val instructions have different source registers.
107    %%
108    %%   {select_val,{y,0},{f,25},{list,[{atom,true},{f,27},{atom,false},{f,29}]}}.
109    %%               ^^^^^                                  ^^^^^^^^^^^^^^^^^^^
110    %% {label,27}.
111    %%   {kill,{y,0}}.
112    %%   {move,{y,2},{x,0}}.
113    %%   {line,...}.
114    %%   {call,1,{f,31}}.
115    %%   {select_val,{x,0},{f,33},{list,[{atom,true},{f,35},{atom,false},{f,29}]}}.
116    %%               ^^^^^                                  ^^^^^^^^^^^^^^^^^^^
117    %% {label,29}.
118    %%   {move,{atom,false},{y,0}}.  <=== REMOVED (unsafely).
119    %%   {jump,{f,37}}.
120
121    Simple = case case Simple0 of
122                      false -> false;
123                      true -> id(Left)
124                  end
125             of
126                 false ->
127                     false;
128                 true ->
129                     id(Right)
130             end,
131    {id({Left,Right,Simple}),Simple}.
132
133unsafe_move_elimination_2(Int) ->
134    %% The type optimization pass would recognize that TagInt can only be
135    %% [0 .. 7], so the first 'case' would select_val over [0 .. 6] and swap
136    %% out the fail label with the block for 7.
137    %%
138    %% A later optimization would merge this block with 'expects_h' in the
139    %% second case, as the latter is only reachable from the former.
140    %%
141    %% ... but this broke down when the move elimination optimization didn't
142    %% take the fail label of the first select_val into account. This caused it
143    %% to believe that the only way to reach 'expects_h' was through the second
144    %% case when 'Tag' =:= 'h', which made it remove the move instruction
145    %% added in the first case, passing garbage to expects_h/2.
146    TagInt = Int band 2#111,
147    Tag = case TagInt of
148              0 -> a;
149              1 -> b;
150              2 -> c;
151              3 -> d;
152              4 -> e;
153              5 -> f;
154              6 -> g;
155              7 -> h
156          end,
157    case Tag of
158        g -> expects_g(TagInt, Tag);
159        h -> expects_h(TagInt, Tag);
160        _ -> Tag = id(Tag), ok
161    end.
162
163expects_g(6, Atom) ->
164    Atom = id(g),
165    ok.
166
167expects_h(7, Atom) ->
168    Atom = id(h),
169    ok.
170
171%% When compiled with +no_copt, beam_validator would complain about
172%% ambigous try/catch state.
173ambiguous_catch_try_state_1(<<42:false>>) ->
174    %% The beam_ssa_bsm pass will duplicate the entire second clause.
175    %% beam_jump will share the blocks with the build_stacktrace
176    %% instructions.
177    [];
178ambiguous_catch_try_state_1(V0) ->
179    try
180        try
181            receive after bad -> timeout end
182        catch
183            _:V0 ->
184                error
185        after
186            ok
187        end
188    of
189        true ->
190            ok
191    catch
192        month:power:V2 ->
193            %% A build_stacktrace instruction would be shared, causing
194            %% an ambiguous try/catch state.
195            V2
196    after
197        a = b
198    end.
199
200ambiguous_catch_try_state_2() ->
201    case
202        try
203            case false = 0 of
204                   false ->
205                       hand
206               end
207        catch
208            idea:[]:V1 ->
209                V1;
210            country:42 ->
211                %% if_end would be shared in an unsafe way.
212                if 0 -> way end after [] end of [] -> if $X -> "D" end
213    end.
214
215ambiguous_catch_try_state_3() ->
216    case
217        try
218            case false = 0 of
219                   false ->
220                       hand
221               end
222        catch
223            idea:[]:V1 ->
224                V1;
225            country:42 ->
226                %% case_end would be shared in an unsafe way.
227                case x of y -> way end after [] end of [] -> case x of $X -> "D" end
228    end.
229
230
231-record(message2, {id, p1}).
232-record(message3, {id, p1, p2}).
233
234build_tuple(_Config) ->
235    {'EXIT',{{badrecord,message3},_}} = (catch do_build_tuple(#message2{})),
236    ok.
237
238do_build_tuple(Message) ->
239    if is_record(Message, message2) ->
240	    Res = {res, rand:uniform(100)},
241	    {Message#message3.id, Res}
242    end.
243
244coverage(_Config) ->
245    ok = coverage_1(ok),
246    {error,badarg} = coverage_1({error,badarg}),
247
248    gt = coverage_2(100, 42),
249    le = coverage_2(100, 999),
250    le = coverage_2([], []),
251    gt = coverage_2([], xxx),
252
253    error = coverage_3(#{key => <<"child">>}),
254    error = coverage_3(#{}),
255    ok.
256
257coverage_1(Var) ->
258    case id(Var) of
259	ok -> ok;
260	Error -> Error
261    end.
262
263%% Cover beam_jump:invert_test(is_ne_exact).
264coverage_2(Pre1, Pre2) ->
265    case
266        case Pre1 == [] of
267            false ->
268                false;
269            true ->
270                Pre2 /= []
271        end
272    of
273        true ->
274            gt;
275        false ->
276            case Pre1 > Pre2 of
277                true ->
278                    gt;
279                false ->
280                    le
281            end
282    end.
283
284coverage_3(#{key := <<child>>}) when false ->
285    ok;
286coverage_3(#{}) ->
287    error.
288
289%% ERIERL-478: The validator failed to validate argument types when calls were
290%% shared and the types at the common block turned out wider than the join of
291%% each individual call site.
292call_sharing(_Config) ->
293    A_2 = {a, 1},
294    A_3 = {a, 1, 2},
295
296    A_2 = cs_1(id(A_2)),
297    A_3 = cs_1(id(A_3)),
298
299    B_2 = {b, 1},
300    B_3 = {b, 1, 2},
301    B_2 = cs_1(id(B_2)),
302    B_3 = cs_1(id(B_3)),
303
304    C_2 = {c, 1},
305    C_3 = {c, 1, 2},
306    {'EXIT',_} = (catch (cs_1(id(C_2)))),
307    {'EXIT',_} = (catch (cs_1(id(C_3)))),
308
309    ok.
310
311cs_1(Key) ->
312    A = case Key of
313            %% Must be a single line to trigger the bug.
314            {Tag, _, _} when Tag == a; Tag == b -> cs_2(Key); {Tag, _} when Tag == a; Tag == b -> cs_2(Key)
315        end,
316    id(A).
317
318cs_2(I) -> I.
319
320undecided_allocation(_Config) ->
321    ok = catch undecided_allocation_1(<<10:(3*7)>>),
322    {'EXIT',{{badrecord,rec},_}} = catch undecided_allocation_1(8),
323    ok.
324
325-record(rec, {}).
326undecided_allocation_1(<<10:3/integer-unit:7>>) ->
327    ok;
328undecided_allocation_1(V) ->
329    %% The record update operation would be duplicated by the beam_ssa_bssm
330    %% pass, and beam_jump would incorrectly share the resulting calls to
331    %% error/1, causing beam_validator to issue the following diagnostic
332    %% when this module was compiled with the no_type_opt option:
333    %%
334    %%  Internal consistency check failed - please report this bug.
335    %%  Instruction: {call_ext,1,{extfunc,erlang,error,1}}
336    %%  Error:       {allocated,undecided}:
337
338    <<
339      <<0>> || <<0:V>> <= <<0>>
340    >>#rec{},
341    if whatever -> [] end.
342
343
344id(I) ->
345    I.
346