diff options
Diffstat (limited to 'lib/stdlib/test/erl_eval_SUITE.erl')
-rw-r--r-- | lib/stdlib/test/erl_eval_SUITE.erl | 314 |
1 files changed, 274 insertions, 40 deletions
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index 0bf59cf60e..faaa9f727f 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2021. All Rights Reserved. +%% Copyright Ericsson AB 1998-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ %% %CopyrightEnd% -module(erl_eval_SUITE). +-feature(maybe_expr, enable). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2, init_per_group/2,end_per_group/2]). @@ -43,6 +44,7 @@ otp_13228/1, otp_14826/1, funs/1, + custom_stacktrace/1, try_catch/1, eval_expr_5/1, zero_width/1, @@ -52,7 +54,8 @@ otp_16439/1, otp_14708/1, otp_16545/1, - otp_16865/1]). + otp_16865/1, + eep49/1]). %% %% Define to run outside of test server @@ -91,8 +94,9 @@ all() -> simple_cases, unary_plus, apply_atom, otp_5269, otp_6539, otp_6543, otp_6787, otp_6977, otp_7550, otp_8133, otp_10622, otp_13228, otp_14826, - funs, try_catch, eval_expr_5, zero_width, - eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545, otp_16865]. + funs, custom_stacktrace, try_catch, eval_expr_5, zero_width, + eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545, otp_16865, + eep49]. groups() -> []. @@ -1000,23 +1004,23 @@ otp_14826(_Config) -> backtrace_check("fun(P) when is_pid(P) -> true end(a).", function_clause, [{erl_eval,'-inside-an-interpreted-fun-',[a],[]}, - {erl_eval,eval_fun,6}, + {erl_eval,eval_fun,8}, ?MODULE]), backtrace_check("B.", {unbound_var, 'B'}, [{erl_eval,expr,2}, ?MODULE]), backtrace_check("B.", {unbound, 'B'}, - [{erl_eval,expr,5}, ?MODULE], + [{erl_eval,expr,6}, ?MODULE], none, none), backtrace_check("1/0.", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), backtrace_catch("catch 1/0.", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), check(fun() -> catch exit(foo) end, "catch exit(foo).", {'EXIT', foo}), @@ -1026,33 +1030,33 @@ otp_14826(_Config) -> backtrace_check("try 1/0 after foo end.", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), backtrace_catch("catch (try 1/0 after foo end).", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), backtrace_catch("try catch 1/0 after foo end.", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), backtrace_check("try a of b -> bar after foo end.", {try_clause,a}, - [{erl_eval,try_clauses,8}]), + [{erl_eval,try_clauses,10}]), check(fun() -> X = try foo:bar() catch A:B:C -> {A,B} end, X end, "try foo:bar() catch A:B:C -> {A,B} end.", {error, undef}), backtrace_check("C = 4, try foo:bar() catch A:B:C -> {A,B,C} end.", stacktrace_bound, - [{erl_eval,check_stacktrace_vars,2}, - {erl_eval,try_clauses,8}], + [{erl_eval,check_stacktrace_vars,5}, + {erl_eval,try_clauses,10}], none, none), backtrace_catch("catch (try a of b -> bar after foo end).", {try_clause,a}, - [{erl_eval,try_clauses,8}]), + [{erl_eval,try_clauses,10}]), backtrace_check("try 1/0 catch exit:a -> foo end.", badarith, [{erlang,'/',[1,0],[]}, - {erl_eval,do_apply,6}]), + {erl_eval,do_apply,7}]), Es = [{'try',1,[{call,1,{remote,1,{atom,1,foo},{atom,1,bar}},[]}], [], [{clause,1,[{tuple,1,[{var,1,'A'},{var,1,'B'},{atom,1,'C'}]}], @@ -1062,8 +1066,8 @@ otp_14826(_Config) -> ct:fail(stacktrace_variable) catch error:{illegal_stacktrace_variable,{atom,1,'C'}}:S -> - [{erl_eval,check_stacktrace_vars,2,_}, - {erl_eval,try_clauses,8,_}|_] = S + [{erl_eval,check_stacktrace_vars,5,_}, + {erl_eval,try_clauses,10,_}|_] = S end, backtrace_check("{1,1} = {A = 1, A = 2}.", {badmatch, 1}, @@ -1073,53 +1077,53 @@ otp_14826(_Config) -> [{erl_eval,guard0,4}], none, none), backtrace_check("case a of foo() -> ok end.", {illegal_pattern,{call,1,{atom,1,foo},[]}}, - [{erl_eval,match,4}], none, none), + [{erl_eval,match,6}], none, none), backtrace_check("case a of b -> ok end.", {case_clause,a}, - [{erl_eval,case_clauses,6}, ?MODULE]), + [{erl_eval,case_clauses,8}, ?MODULE]), backtrace_check("if a =:= b -> ok end.", if_clause, - [{erl_eval,if_clauses,5}, ?MODULE]), + [{erl_eval,if_clauses,7}, ?MODULE]), backtrace_check("fun A(b) -> ok end(a).", function_clause, [{erl_eval,'-inside-an-interpreted-fun-',[a],[]}, - {erl_eval,eval_named_fun,8}, + {erl_eval,eval_named_fun,10}, ?MODULE]), backtrace_check("[A || A <- a].", {bad_generator, a}, - [{erl_eval,eval_generate,7}, {erl_eval, eval_lc, 6}]), + [{erl_eval,eval_generate,8}, {erl_eval, eval_lc, 7}]), backtrace_check("<< <<A>> || <<A>> <= a>>.", {bad_generator, a}, - [{erl_eval,eval_b_generate,7}, {erl_eval, eval_bc, 6}]), + [{erl_eval,eval_b_generate,8}, {erl_eval, eval_bc, 7}]), backtrace_check("[A || A <- [1], begin a end].", {bad_filter, a}, - [{erl_eval,eval_filter,6}, {erl_eval, eval_generate, 7}]), + [{erl_eval,eval_filter,7}, {erl_eval, eval_generate, 8}]), fun() -> {'EXIT', {{badarity, {_Fun, []}}, BT}} = (catch parse_and_run("fun(A) -> A end().")), - check_backtrace([{erl_eval,do_apply,5}, ?MODULE], BT) + check_backtrace([{erl_eval,do_apply,6}, ?MODULE], BT) end(), fun() -> {'EXIT', {{badarity, {_Fun, []}}, BT}} = (catch parse_and_run("fun F(A) -> A end().")), - check_backtrace([{erl_eval,do_apply,5}, ?MODULE], BT) + check_backtrace([{erl_eval,do_apply,6}, ?MODULE], BT) end(), backtrace_check("foo().", undef, - [{erl_eval,foo,0},{erl_eval,local_func,6}], + [{erl_eval,foo,0},{erl_eval,local_func,8}], none, none), backtrace_check("a orelse false.", {badarg, a}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("a andalso false.", {badarg, a}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("t = u.", {badmatch, u}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("{math,sqrt}(2).", {badfun, {math,sqrt}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("erl_eval_SUITE:simple().", simple, [{?MODULE,simple1,0},{?MODULE,simple,0},erl_eval]), @@ -1128,31 +1132,39 @@ otp_14826(_Config) -> "19,20,21,22,23,24,25,26,27,28,29,30) -> a end.", {argument_limit, {'fun',1,[{clause,1,Args,[],[{atom,1,a}]}]}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("fun F(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18," "19,20,21,22,23,24,25,26,27,28,29,30) -> a end.", {argument_limit, {named_fun,1,'F',[{clause,1,Args,[],[{atom,1,a}]}]}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("#r{}.", {undef_record,r}, - [{erl_eval,expr,5}, ?MODULE], + [{erl_eval,expr,6}, ?MODULE], none, none), %% eval_bits backtrace_check("<<100:8/bitstring>>.", badarg, - [{eval_bits,eval_exp_field1,6}, + [{eval_bits,eval_exp_field,6}, eval_bits,eval_bits,erl_eval]), backtrace_check("<<100:8/foo>>.", {undefined_bittype,foo}, - [{eval_bits,make_bit_type,3},eval_bits, + [{eval_bits,make_bit_type,4},eval_bits, eval_bits,eval_bits], none, none), backtrace_check("B = <<\"foo\">>, <<B/binary-unit:7>>.", badarg, - [{eval_bits,eval_exp_field1,6}, + [{eval_bits,eval_exp_field,6}, eval_bits,eval_bits,erl_eval], none, none), + + %% eval_bits with error info + {error_info, #{cause := _, override_segment_position := 1}} = + error_info_catch("<<100:8/bitstring>>.", badarg), + + {error_info, #{cause := _, override_segment_position := 2}} = + error_info_catch("<<0:8, 100:8/bitstring>>.", badarg), + ok. simple() -> @@ -1172,13 +1184,143 @@ simple1() -> WillNeverHappen -> WillNeverHappen end. +custom_stacktrace(Config) when is_list(Config) -> + EFH = {value, fun custom_stacktrace_eval_handler/3}, + + backtrace_check("1 + atom.", badarith, + [{erlang,'+',[1,atom]}, mystack(1)], none, EFH), + backtrace_check("\n1 + atom.", badarith, + [{erlang,'+',[1,atom]}, mystack(2)], none, EFH), + + backtrace_check("lists:flatten(atom).", function_clause, + [{lists,flatten,[atom]}, mystack(1)], none, EFH), + + backtrace_check("invalid andalso true.", {badarg, invalid}, + [mystack(1)], none, EFH), + backtrace_check("invalid orelse true.", {badarg, invalid}, + [mystack(1)], none, EFH), + + backtrace_check("invalid = valid.", {badmatch, valid}, + [erl_eval, mystack(1)], none, EFH), + + backtrace_check("1:2.", {badexpr, ':'}, + [erl_eval, mystack(1)], none, EFH), + + backtrace_check("Unknown.", {unbound, 'Unknown'}, + [erl_eval, mystack(1)], none, EFH), + + backtrace_check("#unknown{}.", {undef_record,unknown}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("#unknown{foo=bar}.", {undef_record,unknown}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("#unknown.index.", {undef_record,unknown}, + [erl_eval, mystack(1)], none, EFH), + + backtrace_check("fun foo/2.", undef, + [{erl_eval, foo, 2}, erl_eval, mystack(1)], none, EFH), + backtrace_check("foo(1, 2).", undef, + [{erl_eval, foo, 2}, erl_eval, mystack(1)], none, EFH), + + fun() -> + {'EXIT', {{badarity, {_Fun, []}}, BT}} = + (catch parse_and_run("fun(A) -> A end().", none, EFH)), + check_backtrace([erl_eval, mystack(1)], BT) + end(), + + fun() -> + {'EXIT', {{badarity, {_Fun, []}}, BT}} = + (catch parse_and_run("fun F(A) -> A end().", none, EFH)), + check_backtrace([erl_eval, mystack(1)], BT) + end(), + + backtrace_check("[X || X <- 1].", {bad_generator, 1}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("[X || <<X>> <= 1].", {bad_generator, 1}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("<<X || X <- 1>>.", {bad_generator, 1}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("<<X || <<X>> <= 1>>.", {bad_generator, 1}, + [erl_eval, mystack(1)], none, EFH), + + backtrace_check("if false -> true end.", if_clause, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("case 0 of 1 -> true end.", {case_clause, 0}, + [erl_eval, mystack(1)], none, EFH), + backtrace_check("try 0 of 1 -> true after ok end.", {try_clause, 0}, + [mystack(1)], none, EFH), + + backtrace_check("fun(0) -> 1 end(1).", function_clause, + [{erl_eval,'-inside-an-interpreted-fun-', [1]}, erl_eval, mystack(1)], + none, EFH), + backtrace_check("fun F(0) -> 1 end(1).", function_clause, + [{erl_eval,'-inside-an-interpreted-fun-', [1]}, erl_eval, mystack(1)], + none, EFH), + + fun() -> + {'EXIT', {{illegal_pattern,_}, BT}} = + (catch parse_and_run("make_ref() = 1.", none, EFH)), + check_backtrace([erl_eval, mystack(1)], BT) + end(), + + %% eval_bits + backtrace_check("<<100:8/bitstring>>.", + badarg, + [{eval_bits,eval_exp_field,6}, mystack(1)], + none, EFH), + backtrace_check("<<100:8/foo>>.", + {undefined_bittype,foo}, + [{eval_bits,make_bit_type,4}, mystack(1)], + none, EFH), + backtrace_check("B = <<\"foo\">>, <<B/binary-unit:7>>.", + badarg, + [{eval_bits,eval_exp_field,6}, mystack(1)], + none, EFH), + + ok. + +mystack(Line) -> + {my_module, my_function, 0, [{file, "evaluator"}, {line, Line}]}. + +custom_stacktrace_eval_handler(Ann, FunOrModFun, Args) -> + try + case FunOrModFun of + {Mod, Fun} -> apply(Mod, Fun, Args); + Fun -> apply(Fun, Args) + end + catch + Kind:Reason:Stacktrace -> + %% Take everything up to the evaluation function + Pruned = + lists:takewhile(fun + ({erl_eval_SUITE,backtrace_check,5,_}) -> false; + (_) -> true + end, Stacktrace), + %% Now we prune any shared code path from erl_eval + {current_stacktrace, Current} = + erlang:process_info(self(), current_stacktrace), + Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned)), + Location = [{file, "evaluator"}, {line, erl_anno:line(Ann)}], + %% Add our file+line information at the bottom + Custom = lists:reverse([{my_module, my_function, 0, Location} | Reversed]), + erlang:raise(Kind, Reason, Custom) + end. + +drop_common([H | T1], [H | T2]) -> drop_common(T1, T2); +drop_common([H | T1], T2) -> drop_common(T1, T2); +drop_common([], [{?MODULE, custom_stacktrace_eval_handler, _, _} | T2]) -> T2; +drop_common([], T2) -> T2. + %% Simple cases, just to cover some code. funs(Config) when is_list(Config) -> do_funs(none, none), do_funs(lfh(), none), + do_funs(none, efh()), do_funs(lfh(), efh()), + do_funs(none, ann_efh()), + do_funs(lfh(), ann_efh()), error_check("nix:foo().", {access_not_allowed,nix}, lfh(), efh()), + error_check("nix:foo().", {access_not_allowed,nix}, lfh(), ann_efh()), error_check("bar().", undef, none, none), check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end, @@ -1217,6 +1359,15 @@ funs(Config) when is_list(Config) -> error_check("apply(timer, sleep, [1]).", got_it, none, EFH), error_check("begin F = fun(T) -> timer:sleep(T) end,F(1) end.", got_it, none, EFH), + + AnnEF = fun(1, {timer,sleep}, As) when length(As) == 1 -> exit({got_it,sleep}); + (1, {M,F}, As) -> apply(M, F, As) + end, + AnnEFH = {value, AnnEF}, + error_check("apply(timer, sleep, [1]).", got_it, none, AnnEFH), + error_check("begin F = fun(T) -> timer:sleep(T) end,F(1) end.", + got_it, none, AnnEFH), + error_check("fun c/1.", undef), error_check("fun a:b/0().", undef), @@ -1398,6 +1549,9 @@ local_func_value(F, As) when is_atom(F) -> efh() -> {value, fun(F, As) -> external_func(F, As) end}. +ann_efh() -> + {value, fun(_Ann, F, As) -> external_func(F, As) end}. + external_func({M,_}, _As) when M == nix -> exit({{access_not_allowed,M},[mfa]}); external_func(F, As) when is_function(F) -> @@ -1756,6 +1910,67 @@ otp_16865(Config) when is_list(Config) -> {badmatch, b}), ok. +eep49(Config) when is_list(Config) -> + check(fun() -> + maybe empty end + end, + "maybe empty end.", + empty), + check(fun() -> + maybe ok ?= ok end + end, + "maybe ok ?= ok end.", + ok), + check(fun() -> + maybe {ok,A} ?= {ok,good}, A end + end, + "maybe {ok,A} ?= {ok,good}, A end.", + good), + check(fun() -> + maybe {ok,A} ?= {ok,good}, {ok,B} ?= {ok,also_good}, {A,B} end + end, + "maybe {ok,A} ?= {ok,good}, {ok,B} ?= {ok,also_good}, {A,B} end.", + {good,also_good}), + check(fun() -> + maybe {ok,A} ?= {ok,good}, {ok,B} ?= {error,wrong}, {A,B} end + end, + "maybe {ok,A} ?= {ok,good}, {ok,B} ?= {error,wrong}, {A,B} end.", + {error,wrong}), + + %% Test maybe ... else ... end. + check(fun() -> + maybe empty else _ -> error end + end, + "maybe empty else _ -> error end.", + empty), + check(fun() -> + maybe ok ?= ok else _ -> error end + end, + "maybe ok ?= ok else _ -> error end.", + ok), + check(fun() -> + maybe ok ?= other else _ -> error end + end, + "maybe ok ?= other else _ -> error end.", + error), + check(fun() -> + maybe {ok,A} ?= {ok,good}, {ok,B} ?= {ok,also_good}, {A,B} + else {error,_} -> error end + end, + "maybe {ok,A} ?= {ok,good}, {ok,B} ?= {ok,also_good}, {A,B} " + "else {error,_} -> error end.", + {good,also_good}), + check(fun() -> + maybe {ok,A} ?= {ok,good}, {ok,B} ?= {error,other}, {A,B} + else {error,_} -> error end + end, + "maybe {ok,A} ?= {ok,good}, {ok,B} ?= {error,other}, {A,B} " + "else {error,_} -> error end.", + error), + error_check("maybe ok ?= simply_wrong else {error,_} -> error end.", + {else_clause,simply_wrong}), + ok. + %% Check the string in different contexts: as is; in fun; from compiled code. check(F, String, Result) -> check1(F, String, Result), @@ -1869,6 +2084,14 @@ backtrace_catch(String, Result, Backtrace) -> ct:fail({eval, Other, Result}) end. +error_info_catch(String, Result) -> + case catch parse_and_run(String) of + {'EXIT', {Result, [{_, _, _, Info}|_]}} -> + lists:keyfind(error_info, 1, Info); + Other -> + ct:fail({eval, Other, Result}) + end. + check_backtrace([B1|Backtrace], [B2|BT]) -> case {B1, B2} of {M, {M,_,_,_}} -> @@ -1887,15 +2110,26 @@ eval_string(String) -> Result. parse_expr(String) -> - {ok,Tokens,_} = erl_scan:string(String), + Tokens = erl_scan_string(String), {ok, [Expr]} = erl_parse:parse_exprs(Tokens), Expr. parse_exprs(String) -> - {ok,Tokens,_} = erl_scan:string(String), + Tokens = erl_scan_string(String), {ok, Exprs} = erl_parse:parse_exprs(Tokens), Exprs. +erl_scan_string(String) -> + %% FIXME: When the experimental features EEP has been implemented, we should + %% dig out all keywords defined in all features. + ResWordFun = + fun('maybe') -> true; + ('else') -> true; + (Other) -> erl_scan:reserved_word(Other) + end, + {ok,Tokens,_} = erl_scan:string(String, 1, [{reserved_word_fun,ResWordFun}]), + Tokens. + parse_and_run(String) -> erl_eval:expr(parse_expr(String), []). |