diff options
Diffstat (limited to 'erts/emulator')
-rw-r--r-- | erts/emulator/beam/erl_nif.c | 71 | ||||
-rw-r--r-- | erts/emulator/beam/erl_nif_api_funcs.h | 4 | ||||
-rw-r--r-- | erts/emulator/test/nif_SUITE.erl | 52 | ||||
-rw-r--r-- | erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 71 |
4 files changed, 137 insertions, 61 deletions
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index 4df34e4a06..45fc949b81 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -445,7 +445,7 @@ int enif_is_list(ErlNifEnv* env, ERL_NIF_TERM term) int enif_is_exception(ErlNifEnv* env, ERL_NIF_TERM term) { - return term == THE_NON_VALUE; + return env->exception_thrown && term == THE_NON_VALUE; } int enif_is_number(ErlNifEnv* env, ERL_NIF_TERM term) @@ -737,12 +737,21 @@ Eterm enif_make_sub_binary(ErlNifEnv* env, ERL_NIF_TERM bin_term, Eterm enif_make_badarg(ErlNifEnv* env) { + return enif_raise_exception(env, am_badarg); +} + +Eterm enif_raise_exception(ErlNifEnv* env, ERL_NIF_TERM reason) +{ env->exception_thrown = 1; - BIF_ERROR(env->proc, BADARG); + env->proc->fvalue = reason; + BIF_ERROR(env->proc, EXC_ERROR); } -int enif_has_pending_exception(ErlNifEnv* env) +int enif_has_pending_exception(ErlNifEnv* env, ERL_NIF_TERM* reason) { + if (env->exception_thrown && reason != NULL) { + *reason = env->proc->fvalue; + } return env->exception_thrown; } @@ -1538,12 +1547,13 @@ int enif_consume_timeslice(ErlNifEnv* env, int percent) * NIF exports need a few more items than the Export struct provides, * including the erl_module_nif* and a NIF function pointer, so the * NifExport below adds those. The Export member must be first in the - * struct. The saved_mfa, saved_argc, nif_level, alloced_argv_sz and argv - * members are used to track the MFA and arguments of the top NIF in case a - * chain of one or more enif_schedule_nif() calls results in an exception, - * since in that case the original MFA and registers have to be restored - * before returning to Erlang to ensure stacktrace information associated - * with the exception is correct. + * struct. The saved_mfa, exception_thrown, saved_argc, rootset_extra, and + * rootset members are used to track the MFA, any pending exception, and + * arguments of the top NIF in case a chain of one or more + * enif_schedule_nif() calls results in an exception, since in that case + * the original MFA and registers have to be restored before returning to + * Erlang to ensure stacktrace information associated with the exception is + * correct. */ typedef ERL_NIF_TERM (*NativeFunPtr)(ErlNifEnv*, int, const ERL_NIF_TERM[]); @@ -1552,25 +1562,28 @@ typedef struct { struct erl_module_nif* m; NativeFunPtr fp; Eterm saved_mfa[3]; + int exception_thrown; int saved_argc; - int alloced_argv_sz; - Eterm argv[1]; + int rootset_extra; + Eterm rootset[1]; } NifExport; /* * If a process has saved arguments, they need to be part of the GC * rootset. The function below is called from setup_rootset() in - * erl_gc.c. This function is declared in erl_process.h. + * erl_gc.c. This function is declared in erl_process.h. Any exception term + * saved in the NifExport is also made part of the GC rootset here; it + * always resides in rootset[0]. */ int erts_setup_nif_gc(Process* proc, Eterm** objv, int* nobj) { NifExport* ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc); - int gc = (ep && ep->saved_argc > 0); + int gc = ep && (ep->saved_argc > 0 || ep->rootset[0] != NIL); if (gc) { - *objv = ep->argv; - *nobj = ep->saved_argc; + *objv = ep->rootset; + *nobj = 1 + ep->saved_argc; } return gc; } @@ -1582,14 +1595,14 @@ static NifExport* allocate_nif_sched_data(Process* proc, int argc) { NifExport* ep; - size_t argv_extra, total; + size_t total; int i; - argv_extra = argc > 1 ? sizeof(Eterm)*(argc-1) : 0; - total = sizeof(NifExport) + argv_extra; + total = sizeof(NifExport) + argc*sizeof(Eterm); ep = erts_alloc(ERTS_ALC_T_NIF_TRAP_EXPORT, total); sys_memset((void*) ep, 0, total); - ep->alloced_argv_sz = argc; + ep->rootset_extra = argc; + ep->rootset[0] = NIL; for (i=0; i<ERTS_NUM_CODE_IX; i++) { ep->exp.addressv[i] = &ep->exp.code[3]; } @@ -1630,15 +1643,22 @@ init_nif_sched_data(ErlNifEnv* env, NativeFunPtr direct_fp, NativeFunPtr indirec ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc); if (!ep) ep = allocate_nif_sched_data(proc, argc); - else if (need_save && ep->alloced_argv_sz < argc) { + else if (need_save && ep->rootset_extra < argc) { NifExport* new_ep = allocate_nif_sched_data(proc, argc); destroy_nif_export(ep); ep = new_ep; } + if (env->exception_thrown) { + ep->exception_thrown = 1; + ep->rootset[0] = env->proc->fvalue; + } else { + ep->exception_thrown = 0; + ep->rootset[0] = NIL; + } ERTS_VBUMP_ALL_REDS(proc); for (i = 0; i < argc; i++) { if (need_save) - ep->argv[i] = reg[i]; + ep->rootset[i+1] = reg[i]; reg[i] = (Eterm) argv[i]; } if (need_save) { @@ -1674,7 +1694,7 @@ restore_nif_mfa(Process* proc, NifExport* ep, int exception) proc->current[2] = ep->saved_mfa[2]; if (exception) for (i = 0; i < ep->saved_argc; i++) - reg[i] = ep->argv[i]; + reg[i] = ep->rootset[i+1]; ep->saved_argc = 0; ep->saved_mfa[0] = THE_NON_VALUE; } @@ -1699,6 +1719,7 @@ dirty_nif_finalizer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ASSERT(!ERTS_SCHEDULER_IS_DIRTY(env->proc->scheduler_data)); ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc); ASSERT(ep); + ASSERT(!ep->exception_thrown); if (ep->fp) restore_nif_mfa(proc, ep, 0); return argv[0]; @@ -1716,9 +1737,10 @@ dirty_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ASSERT(!ERTS_SCHEDULER_IS_DIRTY(env->proc->scheduler_data)); ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc); ASSERT(ep); + ASSERT(ep->exception_thrown); if (ep->fp) restore_nif_mfa(proc, ep, 1); - return enif_make_badarg(env); + return enif_raise_exception(env, ep->rootset[0]); } /* @@ -1843,6 +1865,7 @@ execute_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) NifExport* ep; ERL_NIF_TERM result; + ASSERT(!env->exception_thrown); ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc); ASSERT(ep); ep->fp = NULL; @@ -1855,7 +1878,7 @@ execute_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) * which case we need to restore the original NIF MFA. */ if (ep->fp == NULL) - restore_nif_mfa(proc, ep, is_non_value(result) && proc->freason != TRAP); + restore_nif_mfa(proc, ep, env->exception_thrown); return result; } diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h index bdcbb32c46..e38a016958 100644 --- a/erts/emulator/beam/erl_nif_api_funcs.h +++ b/erts/emulator/beam/erl_nif_api_funcs.h @@ -156,7 +156,8 @@ ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_next, (ErlNifEnv *env, ErlNifMapIte ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_prev, (ErlNifEnv *env, ErlNifMapIterator *iter)); ERL_NIF_API_FUNC_DECL(int, enif_map_iterator_get_pair, (ErlNifEnv *env, ErlNifMapIterator *iter, ERL_NIF_TERM *key, ERL_NIF_TERM *value)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_schedule_nif,(ErlNifEnv*,const char*,int,ERL_NIF_TERM (*)(ErlNifEnv*,int,const ERL_NIF_TERM[]),int,const ERL_NIF_TERM[])); -ERL_NIF_API_FUNC_DECL(int, enif_has_pending_exception, (ErlNifEnv *env)); +ERL_NIF_API_FUNC_DECL(int, enif_has_pending_exception, (ErlNifEnv *env, ERL_NIF_TERM* reason)); +ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM, enif_raise_exception, (ErlNifEnv *env, ERL_NIF_TERM reason)); /* ** ADD NEW ENTRIES HERE (before this comment) !!! @@ -307,6 +308,7 @@ ERL_NIF_API_FUNC_DECL(int,enif_is_on_dirty_scheduler,(ErlNifEnv*)); # define enif_map_iterator_get_pair ERL_NIF_API_FUNC_MACRO(enif_map_iterator_get_pair) # define enif_schedule_nif ERL_NIF_API_FUNC_MACRO(enif_schedule_nif) # define enif_has_pending_exception ERL_NIF_API_FUNC_MACRO(enif_has_pending_exception) +# define enif_raise_exception ERL_NIF_API_FUNC_MACRO(enif_raise_exception) /* ** ADD NEW ENTRIES HERE (before this comment) diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index c35c71dd5b..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]). @@ -1387,7 +1388,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 +1600,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 +1612,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 @@ -1622,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"} @@ -1631,12 +1634,18 @@ dirty_nif_exception(Config) when is_list(Config) -> nif_exception(Config) when is_list(Config) -> ensure_lib_loaded(Config), try - call_nif_exception(), + %% 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(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), @@ -1758,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. @@ -1814,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 d964ae338e..1639e47d61 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; } @@ -1536,9 +1539,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 @@ -1612,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, argv); + 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; @@ -1651,14 +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[]) { - /* ignore return value */ enif_make_badarg(env); - return enif_make_atom(env, "ok"); + ERL_NIF_TERM exc_term; + ERL_NIF_TERM badarg_atom = enif_make_atom(env, "badarg"); + 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) @@ -1693,7 +1722,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 { @@ -1913,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}, |