From 2f38cce79ae1bf318cdc8884bd74d1407778d91d Mon Sep 17 00:00:00 2001 From: Steve Vinoski Date: Thu, 30 Apr 2015 15:46:52 -0400 Subject: Fix for enif_schedule_nif and exceptions Fix a place where part of the implementation of enif_schedule_nif was not using the ErlNifEnv exception_thrown field when it should have been. Also make the result of enif_schedule_nif return false when passed to enif_is_exception, and add an assertion for this to the nif_SUITE.c tests. --- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 3cc9f51ef8..58d9a9a88c 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -1536,9 +1536,12 @@ static ERL_NIF_TERM nif_sched1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv static ERL_NIF_TERM call_nif_schedule(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM result; if (argc != 2) return enif_make_atom(env, "false"); - return enif_schedule_nif(env, "nif_sched1", 0, nif_sched1, argc, argv); + result = enif_schedule_nif(env, "nif_sched1", 0, nif_sched1, argc, argv); + assert(!enif_is_exception(env, result)); + return result; } #ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT -- cgit v1.2.3 From 884afd8efb8672df2889df98b5b94f3dbb53952c Mon Sep 17 00:00:00 2001 From: Steve Vinoski Date: Mon, 27 Apr 2015 23:39:37 -0400 Subject: Enhance enif_has_pending_exception Sverker Eriksson came up with the following idea: to handle a future ability for NIFs to raise more than just badarg exceptions, modify the recently-added enif_has_pending_exception function to take a second argument: a pointer to ERL_NIF_TERM. If this argument is a null pointer, ignore it. Otherwise, if the first argument, an ErlNifEnv*, has an associated exception, set the pointed-to ERL_NIF_TERM of the second argument to the value of the exception term. Add new tests and documentation for this modification. --- erts/emulator/test/nif_SUITE.erl | 18 ++++++++++-------- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 25 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index c35c71dd5b..434d1c38d0 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -1387,7 +1387,7 @@ is_checks(Config) when is_list(Config) -> self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, -18446744073709551616.2e2), try - ?line error = check_is_exception(), + ?line check_is_exception(), ?line throw(expected_badarg) catch error:badarg -> @@ -1599,9 +1599,9 @@ dirty_nif_exception(Config) when is_list(Config) -> N when is_integer(N) -> ensure_lib_loaded(Config), try - %% this checks that the expected exception - %% occurs when the NIF returns the result - %% of enif_make_badarg directly + %% this checks that the expected exception occurs when the + %% dirty NIF returns the result of enif_make_badarg + %% directly call_dirty_nif_exception(1), ?t:fail(expected_badarg) catch @@ -1611,10 +1611,9 @@ dirty_nif_exception(Config) when is_list(Config) -> ok end, try - %% this checks that the expected exception - %% occurs when the NIF calls enif_make_badarg - %% at some point but then returns a value that - %% isn't an exception + %% this checks that the expected exception occurs when the + %% dirty NIF calls enif_make_badarg at some point but then + %% returns a value that isn't an exception call_dirty_nif_exception(0), ?t:fail(expected_badarg) catch @@ -1631,6 +1630,9 @@ dirty_nif_exception(Config) when is_list(Config) -> nif_exception(Config) when is_list(Config) -> ensure_lib_loaded(Config), try + %% this checks that the expected exception occurs when the NIF + %% calls enif_make_badarg at some point but then tries to return a + %% value that isn't an exception call_nif_exception(), ?t:fail(expected_badarg) catch diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 58d9a9a88c..95e361690f 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -883,16 +883,19 @@ static ERL_NIF_TERM check_is(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] * * This function is separate from check_is because it calls enif_make_badarg * and so it must return the badarg exception as its return value. Thus, the - * badarg exception indicates success. Failure is indicated by returning an - * error atom. + * badarg exception indicates success. */ static ERL_NIF_TERM check_is_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM badarg, exc_term; ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); - ERL_NIF_TERM badarg = enif_make_badarg(env); - if (enif_is_exception(env, error_atom)) return error_atom; - if (!enif_is_exception(env, badarg)) return error_atom; - if (!enif_has_pending_exception(env)) return error_atom; + ERL_NIF_TERM badarg_atom = enif_make_atom(env, "badarg"); + assert(!enif_is_exception(env, error_atom)); + badarg = enif_make_badarg(env); + assert(enif_is_exception(env, badarg)); + assert(enif_has_pending_exception(env, NULL)); + assert(enif_has_pending_exception(env, &exc_term)); + assert(enif_is_identical(exc_term, badarg_atom)); return badarg; } @@ -1621,7 +1624,7 @@ static ERL_NIF_TERM call_dirty_nif_exception(ErlNifEnv* env, int argc, const ERL for (i = 1; i < 255; i++) args[i] = enif_make_int(env, i); return enif_schedule_nif(env, "call_dirty_nif_exception", ERL_NIF_DIRTY_JOB_CPU_BOUND, - call_dirty_nif_exception, 255, argv); + call_dirty_nif_exception, 255, args); } case 2: { int return_badarg_directly; @@ -1660,7 +1663,13 @@ static ERL_NIF_TERM call_dirty_nif_zero_args(ErlNifEnv* env, int argc, const ERL */ static ERL_NIF_TERM call_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM exc_term; + ERL_NIF_TERM badarg_atom = enif_make_atom(env, "badarg"); + /* ignore return value */ enif_make_badarg(env); + assert(enif_has_pending_exception(env, NULL)); + assert(enif_has_pending_exception(env, &exc_term)); + assert(enif_is_identical(badarg_atom, exc_term)); return enif_make_atom(env, "ok"); } @@ -1696,7 +1705,7 @@ static ERL_NIF_TERM call_nif_nan_or_inf(ErlNifEnv* env, int argc, const ERL_NIF_ } res = enif_make_double(env, val); assert(enif_is_exception(env, res)); - assert(enif_has_pending_exception(env)); + assert(enif_has_pending_exception(env, NULL)); if (strcmp(arg, "tuple") == 0) { return enif_make_tuple2(env, argv[0], res); } else { -- cgit v1.2.3 From 17735c9f3879145f43a3e4be0369b7117b1b7b84 Mon Sep 17 00:00:00 2001 From: Steve Vinoski Date: Wed, 6 May 2015 23:15:04 -0400 Subject: Add enif_raise_exception Add enif_raise_exception function to allow NIFs to raise error exceptions holding any Erlang terms. This does not replace or deprecate the enif_make_badarg function, though, because raising badarg errors is so idiomatic in NIFs. Reimplement enif_make_badarg on top of enif_raise_exception. Add new tests for enif_raise_exception for both normal and dirty NIFs. Add documentation for enif_raise_exception. --- erts/emulator/test/nif_SUITE.erl | 34 ++++++++++++++---- erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 51 ++++++++++++++++++--------- 2 files changed, 61 insertions(+), 24 deletions(-) (limited to 'erts/emulator/test') diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 434d1c38d0..778f6fd087 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -39,8 +39,9 @@ get_length/1, make_atom/1, make_string/1, reverse_list_test/1, otp_9828/1, otp_9668/1, consume_timeslice/1, dirty_nif/1, dirty_nif_send/1, - dirty_nif_exception/1, nif_schedule/1, - nif_exception/1, nif_nan_and_inf/1, nif_atom_too_long/1 + dirty_nif_exception/1, call_dirty_nif_exception/1, nif_schedule/1, + nif_exception/1, call_nif_exception/1, + nif_nan_and_inf/1, nif_atom_too_long/1 ]). -export([many_args_100/100]). @@ -1621,7 +1622,10 @@ dirty_nif_exception(Config) when is_list(Config) -> [{?MODULE,call_dirty_nif_exception,[0],_}|_] = erlang:get_stacktrace(), ok - end + end, + %% this checks that a dirty NIF can raise various terms as + %% exceptions + ok = nif_raise_exceptions(call_dirty_nif_exception) catch error:badarg -> {skipped,"No dirty scheduler support"} @@ -1633,12 +1637,15 @@ nif_exception(Config) when is_list(Config) -> %% this checks that the expected exception occurs when the NIF %% calls enif_make_badarg at some point but then tries to return a %% value that isn't an exception - call_nif_exception(), + call_nif_exception(0), ?t:fail(expected_badarg) catch error:badarg -> ok - end. + end, + %% this checks that a NIF can raise various terms as exceptions + ok = nif_raise_exceptions(call_nif_exception), + ok. nif_nan_and_inf(Config) when is_list(Config) -> ensure_lib_loaded(Config), @@ -1760,7 +1767,20 @@ check(Exp,Got,Line) -> io:format("CHECK at ~p: Expected ~p but got ~p\n",[Line,Exp,Got]), Got end. - + +nif_raise_exceptions(NifFunc) -> + ExcTerms = [{error, test}, "a string", <<"a binary">>, + 42, [1,2,3,4,5], [{p,1},{p,2},{p,3}]], + lists:foldl(fun(Term, ok) -> + try + erlang:apply(?MODULE,NifFunc,[Term]), + ?t:fail({expected,Term}) + catch + error:Term -> + [{?MODULE,NifFunc,[Term],_}|_] = erlang:get_stacktrace(), + ok + end + end, ok, ExcTerms). %% The NIFs: lib_version() -> undefined. @@ -1816,7 +1836,7 @@ call_dirty_nif(_,_,_) -> ?nif_stub. send_from_dirty_nif(_) -> ?nif_stub. call_dirty_nif_exception(_) -> ?nif_stub. call_dirty_nif_zero_args() -> ?nif_stub. -call_nif_exception() -> ?nif_stub. +call_nif_exception(_) -> ?nif_stub. call_nif_nan_or_inf(_) -> ?nif_stub. call_nif_atom_too_long(_) -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 95e361690f..0191e098db 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -1618,13 +1618,18 @@ static ERL_NIF_TERM call_dirty_nif_exception(ErlNifEnv* env, int argc, const ERL { switch (argc) { case 1: { - ERL_NIF_TERM args[255]; - int i; - args[0] = argv[0]; - for (i = 1; i < 255; i++) - args[i] = enif_make_int(env, i); - return enif_schedule_nif(env, "call_dirty_nif_exception", ERL_NIF_DIRTY_JOB_CPU_BOUND, - call_dirty_nif_exception, 255, args); + int arg; + if (enif_get_int(env, argv[0], &arg) && arg < 2) { + ERL_NIF_TERM args[255]; + int i; + args[0] = argv[0]; + for (i = 1; i < 255; i++) + args[i] = enif_make_int(env, i); + return enif_schedule_nif(env, "call_dirty_nif_exception", ERL_NIF_DIRTY_JOB_CPU_BOUND, + call_dirty_nif_exception, 255, args); + } else { + return enif_raise_exception(env, argv[0]); + } } case 2: { int return_badarg_directly; @@ -1657,20 +1662,32 @@ static ERL_NIF_TERM call_dirty_nif_zero_args(ErlNifEnv* env, int argc, const ERL #endif /* - * Call enif_make_badarg, but don't return its return value. Instead, - * return ok. Result should still be a badarg exception for the erlang - * caller. + * If argv[0] is the integer 0, call enif_make_badarg, but don't return its + * return value. Instead, return ok. Result should still be a badarg + * exception for the erlang caller. + * + * For any other value of argv[0], use it as an exception term and return + * the exception. */ static ERL_NIF_TERM call_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ERL_NIF_TERM exc_term; ERL_NIF_TERM badarg_atom = enif_make_atom(env, "badarg"); - - /* ignore return value */ enif_make_badarg(env); - assert(enif_has_pending_exception(env, NULL)); - assert(enif_has_pending_exception(env, &exc_term)); - assert(enif_is_identical(badarg_atom, exc_term)); - return enif_make_atom(env, "ok"); + int arg; + + if (enif_get_int(env, argv[0], &arg) && arg == 0) { + /* ignore return value */ enif_make_badarg(env); + assert(enif_has_pending_exception(env, NULL)); + assert(enif_has_pending_exception(env, &exc_term)); + assert(enif_is_identical(badarg_atom, exc_term)); + return enif_make_atom(env, "ok"); + } else { + ERL_NIF_TERM exc_retval = enif_raise_exception(env, argv[0]); + assert(enif_has_pending_exception(env, NULL)); + assert(enif_has_pending_exception(env, &exc_term)); + assert(enif_is_identical(argv[0], exc_term)); + return exc_retval; + } } #if !defined(NAN) || !defined(INFINITY) @@ -1925,7 +1942,7 @@ static ErlNifFunc nif_funcs[] = {"call_dirty_nif_exception", 1, call_dirty_nif_exception, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"call_dirty_nif_zero_args", 0, call_dirty_nif_zero_args, ERL_NIF_DIRTY_JOB_CPU_BOUND}, #endif - {"call_nif_exception", 0, call_nif_exception}, + {"call_nif_exception", 1, call_nif_exception}, {"call_nif_nan_or_inf", 1, call_nif_nan_or_inf}, {"call_nif_atom_too_long", 1, call_nif_atom_too_long}, {"is_map_nif", 1, is_map_nif}, -- cgit v1.2.3