From 07470eb538827bec99980aab9c641b672c40ae19 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Wed, 21 Sep 2022 22:33:32 +0900 Subject: [PATCH] mruby-exit.c: make exit() to raise SystemExit exception. Now it allows error handlers to work, unlike `exit!`. --- include/mruby/error.h | 8 +++ mrbgems/mruby-bin-mirb/tools/mirb/mirb.c | 2 + mrbgems/mruby-bin-mruby/tools/mruby/mruby.c | 3 + mrbgems/mruby-exit/src/mruby-exit.c | 72 ++++++++++++++------- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/include/mruby/error.h b/include/mruby/error.h index 93b9d4824..ccf2cdb6e 100644 --- a/include/mruby/error.h +++ b/include/mruby/error.h @@ -21,6 +21,14 @@ struct RException { struct RObject *backtrace; // NULL, RArray or RData }; +/* error that should terminate execution */ +#define MRB_EXC_EXIT 65536 +#define MRB_EXC_EXIT_P(e) ((e)->flags & MRB_EXC_EXIT) +/* retrieve status value from exc; need and */ +#define MRB_EXC_EXIT_STATUS(mrb,e) ((int)mrb_as_int((mrb),mrb_obj_iv_get((mrb),(e),MRB_SYM(status)))) +/* exit with SystemExit status */ +#define MRB_EXC_CHECK_EXIT(mrb,e) do {if (MRB_EXC_EXIT_P(e)) exit(MRB_EXC_EXIT_STATUS((mrb),(e)));} while (0) + #define mrb_exc_ptr(v) ((struct RException*)mrb_ptr(v)) MRB_API mrb_noreturn void mrb_sys_fail(mrb_state *mrb, const char *mesg); diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c index ab09994d2..695c32981 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -674,6 +675,7 @@ main(int argc, char **argv) stack_keep = proc->body.irep->nlocals; /* did an exception occur? */ if (mrb->exc) { + MRB_EXC_CHECK_EXIT(mrb, mrb->exc); p(mrb, mrb_obj_value(mrb->exc), 0); mrb->exc = 0; } diff --git a/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c b/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c index 540b999a5..13003d693 100644 --- a/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c +++ b/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #if defined(_WIN32) || defined(_WIN64) # include /* for setmode */ @@ -366,6 +368,7 @@ main(int argc, char **argv) mrb_gc_arena_restore(mrb, ai); mrbc_context_free(mrb, c); if (mrb->exc) { + MRB_EXC_CHECK_EXIT(mrb, mrb->exc); if (!mrb_undef_p(v)) { mrb_print_error(mrb); } diff --git a/mrbgems/mruby-exit/src/mruby-exit.c b/mrbgems/mruby-exit/src/mruby-exit.c index 726c15341..658d188e6 100644 --- a/mrbgems/mruby-exit/src/mruby-exit.c +++ b/mrbgems/mruby-exit/src/mruby-exit.c @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 @@ -9,48 +12,69 @@ # define EXIT_FAILURE 1 #endif -/* - * call-seq: - * exit(status=false) - * - * Exits the process immediately. No exit handlers are run currently. - * status is returned to the underlying system as the - * exit status. - * - * exit(true) - */ +static int +get_status(mrb_state *mrb) +{ + mrb_value status = mrb_true_value(); + + mrb_get_args(mrb, "|o", &status); + if (mrb_true_p(status)) return EXIT_SUCCESS; + if (mrb_false_p(status)) return EXIT_FAILURE; + return (int)mrb_as_int(mrb, status); +} /* * call-seq: - * exit!(status=false) + * exit(status=true) + * + * Initiates the termination of the Ruby script by raising the + * SystemExit exception. This exception may be caught. The + * optional parameter is used to return a status code to the invoking + * environment. + * + * +true+ and +false+ of _status_ means success and failure + * respectively. The interpretation of other integer values are + * system dependent. + * + * exit(0) + */ +static mrb_value +f_exit(mrb_state *mrb, mrb_value self) +{ + int status = get_status(mrb); + mrb_value exc = mrb_obj_new(mrb, mrb_exc_get_id(mrb, MRB_SYM(SystemExit)), 0, NULL); + struct RException *e = mrb_exc_ptr(exc); + e->flags |= MRB_EXC_EXIT; + mrb_iv_set(mrb, exc, MRB_SYM(status), mrb_int_value(mrb, (mrb_int)status)); + mrb_exc_raise(mrb, exc); + /* not reached */ + return mrb_nil_value(); +} + +/* + * call-seq: + * exit!(status=true) * * Exits the process immediately. No exit handlers are run. * status is returned to the underlying system as the * exit status. * - * exit!(true) + * exit!(0) */ static mrb_value f_exit_bang(mrb_state *mrb, mrb_value self) { - mrb_value status = mrb_true_value(); - int istatus; - - mrb_get_args(mrb, "|o", &status); - istatus = mrb_true_p(status) ? EXIT_SUCCESS : - mrb_false_p(status) ? EXIT_FAILURE : - (int)mrb_int(mrb, status); - exit(istatus); - + exit(get_status(mrb)); /* not reached */ - return status; + return mrb_nil_value(); } void mrb_mruby_exit_gem_init(mrb_state* mrb) { - mrb_define_method(mrb, mrb->kernel_module, "exit", f_exit_bang, MRB_ARGS_OPT(1)); - mrb_define_method(mrb, mrb->kernel_module, "exit!", f_exit_bang, MRB_ARGS_OPT(1)); + mrb_define_class_id(mrb, MRB_SYM(SystemExit), mrb->eException_class); + mrb_define_method_id(mrb, mrb->kernel_module, MRB_SYM(exit), f_exit, MRB_ARGS_OPT(1)); + mrb_define_method_id(mrb, mrb->kernel_module, MRB_SYM_B(exit), f_exit_bang, MRB_ARGS_OPT(1)); } void