aboutsummaryrefslogblamecommitdiffstats
path: root/lib/hipe/test/basic_SUITE_data/basic_exceptions.erl
blob: ba9c03d4bad40a90a07b0feb40bdc0a834ca6040 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                      
                  




                                         
                      


                            

                          
                          
                             

                             
                        
                       
                    


                                                                      
















































                                                                      



























                                                                      













































                                                                      


















                                                                      





















                                                                      







































































































































                                                                           









































































































































                                                                              

















































































































                                                                                                































































































                                                            
%%% -*- erlang-indent-level: 2 -*-
%%%-------------------------------------------------------------------
%%% Author: Kostis Sagonas
%%%
%%% Contains tests that raise exceptions and catch them.
%%%-------------------------------------------------------------------
-module(basic_exceptions).

-export([test/0]).

%% functions used as arguments to spawn/3
-export([bad_guy/2]).

test() ->
  ok = test_catches(),
  ok = test_catch_exit(42),
  ok = test_catch_throw(42),
  ok = test_catch_element(),
  ok = test_catch_crash(),
  ok = test_catch_empty(),
  ok = test_catch_merge(),
  ok = test_catches_merged(),
  ok = test_pending_errors(),
  ok = test_bad_fun_call(),
  ok = test_guard_bif(),
  ok = test_eclectic(),
  ok = test_raise(),
  ok.

%%--------------------------------------------------------------------
%% Written in 2001 by Erik Johansson.

test_catches() ->
  ExitBar = {'EXIT', bar},
  L1 = [ExitBar, ok, ExitBar, {ok, ExitBar}],
  L1 = [t1(), t2(), t3(), t4()],
  badarith = (catch element(1, element(2, t5(a, b)))),
  L2 = [42, ExitBar, ExitBar, {no_exception, ok}],
  L2 = [t5(21, 21), t6(), t7(), t8()],
  ok.

t1() ->
  catch foo().

t2() ->
  V = (catch ok()),
  s(),
  V.

t3() ->
  V = (catch foo()),
  V.

t4() ->
  V1 = ok(),
  V2 = (catch foo()),
  {V1, V2}.

t5(A, B) ->
  catch A + B.

t6() ->
  catch {no_exception, ok(), foo()}.

t7() ->
  catch {no_exception, foo(), ok()}.

t8() ->
  catch {no_exception, ok()}.

foo() ->
  s(),
  exit(bar).

ok() -> s(), ok.

s() -> nada.

%%--------------------------------------------------------------------

test_catch_exit(N) ->
  {'EXIT', N}  = (catch exit(N)),
  {'EXIT', 42} = (catch exit(42)),
  42 = try exit(N) catch exit:R1 -> R1 end,
  42 = try exit(42) catch exit:R2 -> R2 end,
  ok.

%%--------------------------------------------------------------------

test_catch_throw(N) ->
  N  = (catch throw(N)),
  42 = (catch throw(42)),
  42 = try throw(N) catch throw:R1 -> R1 end,
  42 = try throw(42) catch throw:R2 -> R2 end,
  ok.

%%--------------------------------------------------------------------

test_catch_element() ->
  'EXIT' = test_catch_element([]),
  'EXIT' = test_catch_element(42),
  ok.

test_catch_element(N) ->
  element(1, catch element(N, {1,2,3,4,5,6,7,8,9,10,11})).

%%--------------------------------------------------------------------

-define(try_match(E),
        catch ?MODULE:non_existing(),
	{'EXIT', {{badmatch, nomatch}, _}} = (catch E = no_match())).

test_catch_crash() ->
  ?try_match(a),
  ?try_match(42),
  ?try_match({a, b, c}),
  ?try_match([]),
  ?try_match(1.0),
  ok.

no_match() -> nomatch.

%% small_test() ->
%%   catch ?MODULE:non_existing(),
%%   io:format("Before\n",[]),
%%   hipe_bifs:show_nstack(self()),
%%   io:format("After\n",[]),
%%   garbage_collect().

%%--------------------------------------------------------------------
%% Tests whether the HiPE compiler optimizes catches in a way that
%% does not result in an infinite loop.
%%--------------------------------------------------------------------

test_catch_empty() ->
  badmatch().

badmatch() ->
  Big = ret_big(),
  Float = ret_float(),
  catch a = Big,
  catch b = Float,
  ok = case Big of Big -> ok end,
  ok = case Float of Float -> ok end,
  ok.

ret_big() ->
  329847987298478924982978248748729829487298292982972978239874.

ret_float() ->
  3.1415927.

%%--------------------------------------------------------------------
%% Test that shows how BEAM can merge catch-end blocks that belong to
%% different catch-start instructions. Written by Richard Carlsson.
%%--------------------------------------------------------------------

test_catch_merge() ->
  merge(get(whatever)).

merge(foo=X) ->
  catch f(X),
  catch g(X);
merge(X) ->
  catch f(X),
  catch g(X).

f(_) -> ok.

g(_) -> ok.

%%--------------------------------------------------------------------
%% Written by Tobias Lindahl.

test_catches_merged() ->
  {'EXIT', _} = merged_catches(foo),
  {'EXIT', {badarith, _}} = merged_catches(bar),
  {'EXIT', _} = merged_catches(baz),
  ok.

merged_catches(X) ->
  case X of
    foo -> catch fail1(0);
    bar -> catch {catch(1 = X), fail2(0)};
    baz -> catch fail3(0)
  end.

fail1(X) -> 1/X.

fail2(X) -> 1/X.

fail3(X) -> 1/X.

%%--------------------------------------------------------------------
%% Taken from exception_SUITE.erl
%%--------------------------------------------------------------------

test_pending_errors() ->
  error_logger:tty(false),    % disable printouts of error reports
  pending_errors().

%% Test various exceptions, in the presence of a previous error
%% suppressed in a guard.
pending_errors() ->
  pending(e_badmatch, {badmatch, b}),
  pending(x, function_clause),
  pending(e_case, {case_clause, xxx}),
  pending(e_if, if_clause),
  %% pending(e_badarith, badarith),
  %% pending(e_undef, undef),
  pending(e_timeoutval, timeout_value),
  %% pending(e_badarg, badarg),
  %% pending(e_badarg_spawn, badarg),
  ok.

bad_guy(pe_badarith, Other) when Other+1 =:= 0 -> % badarith (suppressed)
  ok;
bad_guy(pe_badarg, Other) when length(Other) > 0 -> % badarg (suppressed)
  ok;
bad_guy(_, e_case) ->
  case xxx() of
    ok -> ok
  end;                                        % case_clause
bad_guy(_, e_if) ->
  B = b(),
  if
    a == B -> ok
  end;                                        % if_clause
%% bad_guy(_, e_badarith) ->
%%   1+b;                                        % badarith
bad_guy(_, e_undef) ->
  non_existing_module:foo();                  % undef
bad_guy(_, e_timeoutval) ->
  receive
  after gazonk -> ok                          % timeout_value
  end;
bad_guy(_, e_badarg) ->
  node(xxx);                                  % badarg
bad_guy(_, e_badarg_spawn) ->
  spawn({}, {}, {});                          % badarg
bad_guy(_, e_badmatch) ->
  a = b().                                    % badmatch

xxx() -> xxx.

b() -> b.

pending(Arg, Expected) ->
  pending(pe_badarith, Arg, Expected),
  pending(pe_badarg, Arg, Expected).

pending(First, Second, Expected) ->
  pending_catched(First, Second, Expected),
  pending_exit_message([First, Second], Expected).

pending_catched(First, Second, Expected) ->
  %% ok = io:format("Catching bad_guy(~p, ~p)\n", [First, Second]),
  case catch bad_guy(First, Second) of
    {'EXIT', Reason} ->
      pending(Reason, bad_guy, [First, Second], Expected);
    Other ->
      exit({not_exit, Other})
  end.

pending_exit_message(Args, Expected) ->
  %% ok = io:format("Trapping exits from spawn_link(~p, ~p, ~p)\n",
  %%                [?MODULE, bad_guy, Args]),
  process_flag(trap_exit, true),
  Pid = spawn_link(?MODULE, bad_guy, Args),
  receive
    {'EXIT', Pid, Reason} ->
      pending(Reason, bad_guy, Args, Expected);
    Other ->
      exit({unexpected_message, Other})
  after 10000 ->
      exit(timeout)
  end,
  process_flag(trap_exit, false).

%% pending({badarg, [{erlang,Bif,BifArgs},{?MODULE,Func,Arity}|_]},
%%      Func, Args, _Code)
%%   when atom(Bif), list(BifArgs), length(Args) =:= Arity ->
%%     ok;
pending({badarg,Trace}, _, _, _) when is_list(Trace) ->
  ok;
%% pending({undef,[{non_existing_module,foo,[]}|_]}, _, _, _) ->
%%   ok;
pending({undef,Trace}, _, _, _)  when is_list(Trace) ->
  ok;
%% pending({function_clause,[{?MODULE,Func,Args}|_]}, Func, Args, _Code) ->
%%   ok;
pending({function_clause,Trace}, _, _, _) when is_list(Trace) ->
  ok;
%% pending({Code,[{?MODULE,Func,Arity}|_]}, Func, Args, Code)
%%   when length(Args) =:= Arity ->
%%     ok;
pending({Code,Trace}, _, _, Code) when is_list(Trace) ->
  ok;
pending(Reason, _Function, _Args, _Code) ->
  exit({bad_exit_reason, Reason}).

%%--------------------------------------------------------------------
%% Taken from fun_SUITE.erl
%%
%% Checks correct exception throwing when calling a bad fun.
%%--------------------------------------------------------------------

test_bad_fun_call() ->
  ok = bad_call_fc(42),
  ok = bad_call_fc(xx),
  ok = bad_call_fc({}),
  ok = bad_call_fc({1}),
  ok = bad_call_fc({1,2,3}),
  ok = bad_call_fc({1,2,3}),
  ok = bad_call_fc({1,2,3,4}),
  ok = bad_call_fc({1,2,3,4,5,6}),
  ok = bad_call_fc({1,2,3,4,5}),
  ok = bad_call_fc({1,2}),
  ok.

bad_call_fc(Fun) ->
  Args = [some, stupid, args],
  Res = (catch Fun(Fun(Args))),
  case Res of
    {'EXIT', {{badfun, Fun} ,_Where}} ->
      ok; %% = io:format("~p(~p) -> ~p\n", [Fun, Args, Res]);
    Other ->
      io:format("~p(~p) -> ~p\n", [Fun, Args, Res]),
      exit({bad_result, Other})
  end.

%%--------------------------------------------------------------------
%% Taken from guard_SUITE.erl
%%
%% Tests correct handling of exceptions by calling guard BIFs with
%% nasty (but legal arguments).
%%--------------------------------------------------------------------

test_guard_bif() ->
  Big = -237849247829874297658726487367328971246284736473821617265433,
  Float = 387924.874,

  %% Succeding use of guard bifs.

  try_gbif('abs/1', Big, -Big),
  try_gbif('float/1', Big, float(Big)),
  try_gbif('trunc/1', Float, 387924.0),
  try_gbif('round/1', Float, 387925.0),
  try_gbif('length/1', [], 0),

  try_gbif('length/1', [a], 1),
  try_gbif('length/1', [a, b], 2),
  try_gbif('length/1', lists:seq(0, 31), 32),

  try_gbif('hd/1', [a], a),
  try_gbif('hd/1', [a, b], a),

  try_gbif('tl/1', [a], []),
  try_gbif('tl/1', [a, b], [b]),
  try_gbif('tl/1', [a, b, c], [b, c]),

  try_gbif('size/1', {}, 0),
  try_gbif('size/1', {a}, 1),
  try_gbif('size/1', {a, b}, 2),
  try_gbif('size/1', {a, b, c}, 3),
  try_gbif('size/1', list_to_binary([]), 0),
  try_gbif('size/1', list_to_binary([1]), 1),
  try_gbif('size/1', list_to_binary([1, 2]), 2),
  try_gbif('size/1', list_to_binary([1, 2, 3]), 3),

  try_gbif('element/2', {x}, {1, x}),
  try_gbif('element/2', {x, y}, {1, x}),
  try_gbif('element/2', {x, y}, {2, y}),

  try_gbif('self/0', 0, self()),
  try_gbif('node/0', 0, node()),
  try_gbif('node/1', self(), node()),

  %% Failing use of guard bifs.

  try_fail_gbif('abs/1', Big, 1),
  try_fail_gbif('abs/1', [], 1),

  try_fail_gbif('float/1', Big, 42),
  try_fail_gbif('float/1', [], 42),

  try_fail_gbif('trunc/1', Float, 0.0),
  try_fail_gbif('trunc/1', [], 0.0),

  try_fail_gbif('round/1', Float, 1.0),
  try_fail_gbif('round/1', [], a),

  try_fail_gbif('length/1', [], 1),
  try_fail_gbif('length/1', [a], 0),
  try_fail_gbif('length/1', a, 0),
  try_fail_gbif('length/1', {a}, 0),

  try_fail_gbif('hd/1', [], 0),
  try_fail_gbif('hd/1', [a], x),
  try_fail_gbif('hd/1', x, x),

  try_fail_gbif('tl/1', [], 0),
  try_fail_gbif('tl/1', [a], x),
  try_fail_gbif('tl/1', x, x),

  try_fail_gbif('size/1', {}, 1),
  try_fail_gbif('size/1', [], 0),
  try_fail_gbif('size/1', [a], 1),
  try_fail_gbif('size/1', fun() -> 1 end, 0),
  try_fail_gbif('size/1', fun() -> 1 end, 1),

  try_fail_gbif('element/2', {}, {1, x}),
  try_fail_gbif('element/2', {x}, {1, y}),
  try_fail_gbif('element/2', [], {1, z}),

  try_fail_gbif('self/0', 0, list_to_pid("<0.0.0>")),
  try_fail_gbif('node/0', 0, xxxx),
  try_fail_gbif('node/1', self(), xxx),
  try_fail_gbif('node/1', yyy, xxx),
  ok.

try_gbif(Id, X, Y) ->
  case guard_bif(Id, X, Y) of
    {Id, X, Y} ->
      %% io:format("guard_bif(~p, ~p, ~p) -- ok\n", [Id, X, Y]);
      ok;
    Other ->
      ok = io:format("guard_bif(~p, ~p, ~p) -- bad result: ~p\n",
		     [Id, X, Y, Other]),
      exit({bad_result,try_gbif})
  end.

try_fail_gbif(Id, X, Y) ->
  case catch guard_bif(Id, X, Y) of
    %% {'EXIT', {function_clause,[{?MODULE,guard_bif,[Id,X,Y]}|_]}} ->
    {'EXIT', {function_clause,_}} ->  % in HiPE, a trace is not generated
      %% io:format("guard_bif(~p, ~p, ~p) -- ok\n", [Id,X,Y]);
      ok;
    Other ->
      ok = io:format("guard_bif(~p, ~p, ~p) -- bad result: ~p\n",
			   [Id, X, Y, Other]),
      exit({bad_result,try_fail_gbif})
  end.

guard_bif('abs/1', X, Y) when abs(X) == Y ->
  {'abs/1', X, Y};
guard_bif('float/1', X, Y) when float(X) == Y ->
  {'float/1', X, Y};
guard_bif('trunc/1', X, Y) when trunc(X) == Y ->
  {'trunc/1', X, Y};
guard_bif('round/1', X, Y) when round(X) == Y ->
  {'round/1', X, Y};
guard_bif('length/1', X, Y) when length(X) == Y ->
  {'length/1', X, Y};
guard_bif('hd/1', X, Y) when hd(X) == Y ->
  {'hd/1', X, Y};
guard_bif('tl/1', X, Y) when tl(X) == Y ->
  {'tl/1', X, Y};
guard_bif('size/1', X, Y) when size(X) == Y ->
  {'size/1', X, Y};
guard_bif('element/2', X, {Pos, Expected}) when element(Pos, X) == Expected ->
  {'element/2', X, {Pos, Expected}};
guard_bif('self/0', X, Y) when self() == Y ->
  {'self/0', X, Y};
guard_bif('node/0', X, Y) when node() == Y ->
  {'node/0', X, Y};
guard_bif('node/1', X, Y) when node(X) == Y ->
  {'node/1', X, Y}.

%%--------------------------------------------------------------------
%% Taken from trycatch_SUITE.erl (compiler test suite).
%%
%% Cases that are commented out contain exception information that was
%% added to Erlang/OTP in commit e8d45ae14c6c3bdfcbbc7964228b004ef4f11ea6
%% (May 2017) only in the BEAM emulator.  Thus, part of this test fails
%% when compiled in native code.
%% The remaining cases are uncommented so that they are properly tested
%% in native code too.
%%--------------------------------------------------------------------

test_eclectic() ->
  V = {make_ref(),3.1415926535,[[]|{}]},
  {{value,{value,V},V},V} =
    eclectic_1({foo,{value,{value,V}}}, undefined, {value,V}),
  {{'EXIT',{V,[{?MODULE,foo,1,_}|_]}},V} =
    eclectic_1({catch_foo,{error,V}}, undefined, {value,V}),
  {{error,{exit,V},{'EXIT',V}},V} =
    eclectic_1({foo,{error,{exit,V}}}, error, {value,V}),
  %% {{value,{value,V},V},
  %%        {'EXIT',{badarith,[{erlang,'+',[0,a],_},{?MODULE,my_add,2,_}|_]}}} =
  %%     eclectic_1({foo,{value,{value,V}}}, undefined, {'add',{0,a}}),
  {{'EXIT',V},V} =
    eclectic_1({catch_foo,{exit,V}}, undefined, {throw,V}),
  %% {{error,{'div',{1,0}},{'EXIT',{badarith,[{erlang,'div',[1,0],_},{?MODULE,my_div,2,_}|_]}}},
  %%        {'EXIT',V}} =
  %%     eclectic_1({foo,{error,{'div',{1,0}}}}, error, {exit,V}),
  {{{error,V},{'EXIT',{V,[{?MODULE,foo,1,_}|_]}}},
   {'EXIT',V}} =
    eclectic_1({catch_foo,{throw,{error,V}}}, undefined, {exit,V}),
  %%
  {{value,{value,{value,V},V}},V} =
    eclectic_2({value,{value,V}}, undefined, {value,V}),
  {{value,{throw,{value,V},V}},V} =
    eclectic_2({throw,{value,V}}, throw, {value,V}),
  {{caught,{'EXIT',V}},undefined} =
    eclectic_2({value,{value,V}}, undefined, {exit,V}),
  {{caught,{'EXIT',{V,[{?MODULE,foo,1,_}|_]}}},undefined} =
    eclectic_2({error,{value,V}}, throw, {error,V}),
  %% The following fails in native code
  %% %% {{caught,{'EXIT',{badarg,[{erlang,abs,[V],_}|_]}}},V} =
  %% %%     eclectic_2({value,{'abs',V}}, undefined, {value,V}),
  %% {{caught,{'EXIT',{badarith,[{erlang,'+',[0,a],_},{?MODULE,my_add,2,_}|_]}}},V} =
  %%     eclectic_2({exit,{'add',{0,a}}}, exit, {value,V}),
  {{caught,{'EXIT',V}},undefined} =
    eclectic_2({value,{error,V}}, undefined, {exit,V}),
  {{caught,{'EXIT',{V,[{?MODULE,foo,1,_}|_]}}},undefined} =
    eclectic_2({throw,{'div',{1,0}}}, throw, {error,V}),
  ok.

eclectic_1(X, C, Y) ->
  erase(eclectic),
  Done = make_ref(),
  Try =
    try case X of
          {catch_foo,V} -> catch {Done,foo(V)};
          {foo,V} -> {Done,foo(V)}
        end of
        {Done,D} -> {value,D,catch foo(D)};
        {'EXIT',_}=Exit -> Exit;
        D -> {D,catch foo(D)}
    catch
      C:D -> {C,D,catch foo(D)}
    after
      put(eclectic, catch foo(Y))
    end,
  {Try,erase(eclectic)}.

eclectic_2(X, C, Y) ->
  Done = make_ref(),
  erase(eclectic),
  Catch =
    case
      catch
        {Done,
         try foo(X) of
             V -> {value,V,foo(V)}
         catch
           C:D -> {C,D,foo(D)}
         after
           put(eclectic, foo(Y))
         end} of
        {Done,Z} -> {value,Z};
        Z -> {caught,Z}
      end,
  {Catch,erase(eclectic)}.

foo({value,Value}) -> Value;
foo({'div',{A,B}}) ->
  my_div(A, B);
foo({'add',{A,B}}) ->
  my_add(A, B);
foo({'abs',X}) ->
  my_abs(X);
foo({error,Error}) ->
  erlang:error(Error);
foo({throw,Throw}) ->
  erlang:throw(Throw);
foo({exit,Exit}) ->
  erlang:exit(Exit);
foo({raise,{Class,Reason}}) ->
  erlang:raise(Class, Reason);
foo(Term) when not is_atom(Term) -> Term.
%%foo(Atom) when is_atom(Atom) -> % must not be defined!

my_div(A, B) ->
  A div B.

my_add(A, B) ->
  A + B.

my_abs(X) ->
  abs(X).

test_raise() ->
  test_raise(fun() -> exit({exit,tuple}) end),
  test_raise(fun() -> abs(id(x)) end),
  test_raise(fun() -> throw({was,thrown}) end),

  badarg = bad_raise(fun() -> abs(id(x)) end),

  ok.

bad_raise(Expr) ->
  try
    Expr()
  catch
    _:E:Stk ->
      erlang:raise(bad_class, E, Stk)
  end.

test_raise(Expr) ->
  test_raise_1(Expr),
  test_raise_2(Expr),
  test_raise_3(Expr).

test_raise_1(Expr) ->
  erase(exception),
  try
    do_test_raise_1(Expr)
  catch
    C:E:Stk ->
      {C,E,Stk} = erase(exception)
  end.

do_test_raise_1(Expr) ->
  try
    Expr()
  catch
    C:E:Stk ->
      %% Here the stacktrace must be built.
      put(exception, {C,E,Stk}),
      erlang:raise(C, E, Stk)
  end.

test_raise_2(Expr) ->
  erase(exception),
  try
    do_test_raise_2(Expr)
  catch
    C:E:Stk ->
      {C,E} = erase(exception),
      try
        Expr()
      catch
        _:_:S ->
          [StkTop|_] = S,
          [StkTop|_] = Stk
      end
  end.

do_test_raise_2(Expr) ->
  try
    Expr()
  catch
    C:E:Stk ->
      %% Here it is possible to replace erlang:raise/3 with
      %% the raw_raise/3 instruction since the stacktrace is
      %% not actually used.
      put(exception, {C,E}),
      erlang:raise(C, E, Stk)
  end.

test_raise_3(Expr) ->
  try
    do_test_raise_3(Expr)
  catch
    exit:{exception,C,E}:Stk ->
      try
        Expr()
      catch
        C:E:S ->
          [StkTop|_] = S,
          [StkTop|_] = Stk
      end
  end.

do_test_raise_3(Expr) ->
  try
    Expr()
  catch
    C:E:Stk ->
      %% Here it is possible to replace erlang:raise/3 with
      %% the raw_raise/3 instruction since the stacktrace is
      %% not actually used.
      erlang:raise(exit, {exception,C,E}, Stk)
  end.

id(I) -> I.