From 0d7718896cfb629ad823b9ca5004465ef2063ab8 Mon Sep 17 00:00:00 2001 From: sorah Date: Tue, 12 Dec 2017 11:47:16 +0000 Subject: error.c(exc_full_message): Exception#full_message Add a method to retrieve a String expression of an exception, formatted in the same way that Ruby prints an uncaught exception out. [Feature #14141] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61154 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- NEWS | 4 ++ error.c | 20 +++++++ eval_error.c | 127 ++++++++++++++++++++++++++------------------ test/ruby/test_exception.rb | 11 ++++ 4 files changed, 109 insertions(+), 53 deletions(-) diff --git a/NEWS b/NEWS index eafd37acaf..ee9ed54eed 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,10 @@ with all sufficient information, see the ChangeLog file or Redmine * Now deprecated [Feature #3072] +* Exception + + * Exception#full_message [Feature #14141] [experimental] + * Dir * Dir.glob provides new optional keyword argument, :base. diff --git a/error.c b/error.c index d257028ad2..4b921f99ff 100644 --- a/error.c +++ b/error.c @@ -921,6 +921,25 @@ exc_to_s(VALUE exc) return rb_String(mesg); } +/* + * call-seq: + * exception.full_message -> string + * + * Returns formatted string of exception. + * The returned string is formatted using the same format that Ruby uses + * when printing an uncaught exceptions to stderr. So it may differ by + * $stderr.tty? at the timing of a call. + */ + +static VALUE +exc_full_message(VALUE exc) +{ + VALUE str = rb_str_new2(""); + VALUE errat = rb_get_backtrace(exc); + rb_ec_error_write(exc, errat, str); + return str; +} + /* * call-seq: * exception.message -> string @@ -2189,6 +2208,7 @@ Init_Exception(void) rb_define_method(rb_eException, "==", exc_equal, 1); rb_define_method(rb_eException, "to_s", exc_to_s, 0); rb_define_method(rb_eException, "message", exc_message, 0); + rb_define_method(rb_eException, "full_message", exc_full_message, 0); rb_define_method(rb_eException, "inspect", exc_inspect, 0); rb_define_method(rb_eException, "backtrace", exc_backtrace, 0); rb_define_method(rb_eException, "backtrace_locations", exc_backtrace_locations, 0); diff --git a/eval_error.c b/eval_error.c index 4c79bf044e..f602737dd2 100644 --- a/eval_error.c +++ b/eval_error.c @@ -4,25 +4,38 @@ */ #ifdef HAVE_BUILTIN___BUILTIN_CONSTANT_P +#define write_warn(str, x) RB_GNUC_EXTENSION_BLOCK( \ + NIL_P(str) ? \ + warn_print(x) : ( \ + (__builtin_constant_p(x)) ? \ + rb_str_concat((str), rb_str_new((x), (long)strlen(x))) : \ + rb_str_concat((str), rb_str_new2(x)) \ + ) \ + ) #define warn_print(x) RB_GNUC_EXTENSION_BLOCK( \ (__builtin_constant_p(x)) ? \ rb_write_error2((x), (long)strlen(x)) : \ rb_write_error(x) \ ) #else +#define write_warn(str, x) NIL_P(str) ? rb_write_error((x)) : rb_str_concat((str), rb_str_new2(x)) #define warn_print(x) rb_write_error(x) #endif + +#define write_warn2(str,x,l) NIL_P(str) ? warn_print2(x,l) : rb_str_concat((str), rb_str_new((x),(l))) #define warn_print2(x,l) rb_write_error2((x),(l)) + +#define write_warn_str(str,x) NIL_P(str) ? rb_write_error_str(x) : rb_str_concat((str), (x)) #define warn_print_str(x) rb_write_error_str(x) static VALUE error_pos_str(void); static void -error_pos(void) +error_pos(const VALUE str) { - VALUE str = error_pos_str(); - if (!NIL_P(str)) { - warn_print_str(str); + VALUE pos = error_pos_str(); + if (!NIL_P(pos)) { + write_warn_str(str, pos); } } @@ -73,7 +86,7 @@ error_print(rb_execution_context_t *ec) } static void -print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, int colored) +print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, const VALUE str, int colored) { static const char underline[] = "\033[4;1m"; static const char bold[] = "\033[1m"; @@ -85,14 +98,14 @@ print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, int colo if (emesg != Qundef) { if (NIL_P(errat) || RARRAY_LEN(errat) == 0 || NIL_P(mesg = RARRAY_AREF(errat, 0))) { - error_pos(); + error_pos(str); } else { - warn_print_str(mesg); - warn_print(": "); + write_warn_str(str, mesg); + write_warn(str, ": "); } - if (colored) warn_print(bold); + if (colored) write_warn(str, bold); if (!NIL_P(emesg)) { einfo = RSTRING_PTR(emesg); @@ -101,17 +114,17 @@ print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, int colo } if (eclass == rb_eRuntimeError && elen == 0) { - if (colored) warn_print(underline); - warn_print("unhandled exception\n"); + if (colored) write_warn(str, underline); + write_warn(str, "unhandled exception\n"); } else { VALUE epath; epath = rb_class_name(eclass); if (elen == 0) { - if (colored) warn_print(underline); - warn_print_str(epath); - warn_print("\n"); + if (colored) write_warn(str, underline); + write_warn_str(str, epath); + write_warn(str, "\n"); } else { const char *tail = 0; @@ -123,26 +136,26 @@ print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, int colo len = tail - einfo; tail++; /* skip newline */ } - warn_print_str(tail ? rb_str_subseq(emesg, 0, len) : emesg); + write_warn_str(str, tail ? rb_str_subseq(emesg, 0, len) : emesg); if (epath) { - warn_print(" ("); - if (colored) warn_print(underline); - warn_print_str(epath); - if (colored) warn_print(reset); - if (colored) warn_print(bold); - warn_print(")\n"); + write_warn(str, " ("); + if (colored) write_warn(str, underline); + write_warn_str(str, epath); + if (colored) write_warn(str, reset); + if (colored) write_warn(str, bold); + write_warn(str, ")\n"); } if (tail) { - warn_print_str(rb_str_subseq(emesg, tail - einfo, elen - len - 1)); + write_warn_str(str, rb_str_subseq(emesg, tail - einfo, elen - len - 1)); } - if (tail ? einfo[elen-1] != '\n' : !epath) warn_print2("\n", 1); + if (tail ? einfo[elen-1] != '\n' : !epath) write_warn2(str, "\n", 1); } } - if (colored) warn_print(reset); + if (colored) write_warn(str, reset); } static void -print_backtrace(const VALUE eclass, const VALUE errat, int reverse) +print_backtrace(const VALUE eclass, const VALUE errat, const VALUE str, int reverse) { if (!NIL_P(errat)) { long i; @@ -161,12 +174,12 @@ print_backtrace(const VALUE eclass, const VALUE errat, int reverse) for (i = 1; i < len; i++) { VALUE line = RARRAY_AREF(errat, reverse ? len - i : i); if (RB_TYPE_P(line, T_STRING)) { - VALUE str = rb_str_new_cstr("\t"); - if (reverse) rb_str_catf(str, "%*ld: ", width, len - i); - warn_print_str(rb_str_catf(str, "from %"PRIsVALUE"\n", line)); + VALUE bt = rb_str_new_cstr("\t"); + if (reverse) rb_str_catf(bt, "%*ld: ", width, len - i); + write_warn_str(str, rb_str_catf(bt, "from %"PRIsVALUE"\n", line)); } if (skip && i == TRACE_HEAD && len > TRACE_MAX) { - warn_print_str(rb_sprintf("\t ... %ld levels...\n", + write_warn_str(str, rb_sprintf("\t ... %ld levels...\n", len - TRACE_HEAD - TRACE_TAIL)); i = len - TRACE_TAIL; } @@ -175,26 +188,16 @@ print_backtrace(const VALUE eclass, const VALUE errat, int reverse) } void -rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) +rb_ec_error_write(volatile VALUE errinfo, volatile VALUE errat, volatile VALUE str) { - volatile VALUE errat = Qundef; - volatile int raised_flag = ec->raised_flag; volatile VALUE eclass = Qundef, emesg = Qundef; if (NIL_P(errinfo)) return; - rb_ec_raised_clear(ec); - EC_PUSH_TAG(ec); - if (EC_EXEC_TAG() == TAG_NONE) { - errat = rb_get_backtrace(errinfo); - } - else if (errat == Qundef) { + if (errat == Qundef) { errat = Qnil; } - else if (eclass == Qundef || emesg != Qundef) { - goto error; - } if ((eclass = CLASS_OF(errinfo)) != Qundef) { VALUE e = rb_check_funcall(errinfo, rb_intern("message"), 0, 0); if (e != Qundef) { @@ -203,15 +206,33 @@ rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) } } if (rb_stderr_tty_p()) { - warn_print("\033[1mTraceback \033[m(most recent call last):\n"); - print_backtrace(eclass, errat, TRUE); - print_errinfo(eclass, errat, emesg, TRUE); + write_warn(str, "\033[1mTraceback \033[m(most recent call last):\n"); + print_backtrace(eclass, errat, str, TRUE); + print_errinfo(eclass, errat, emesg, str, TRUE); } else { - print_errinfo(eclass, errat, emesg, FALSE); - print_backtrace(eclass, errat, FALSE); + print_errinfo(eclass, errat, emesg, str, FALSE); + print_backtrace(eclass, errat, str, FALSE); } - error: +} + +void +rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) +{ + volatile int raised_flag = ec->raised_flag; + volatile VALUE errat; + + if (NIL_P(errinfo)) + return; + rb_ec_raised_clear(ec); + + EC_PUSH_TAG(ec); + if (EC_EXEC_TAG() == TAG_NONE) { + errat = rb_get_backtrace(errinfo); + } + + rb_ec_error_write(errinfo, errat, Qnil); + EC_POP_TAG(); ec->errinfo = errinfo; rb_ec_raised_set(ec, raised_flag); @@ -290,28 +311,28 @@ error_handle(int ex) break; case TAG_RETURN: - error_pos(); + error_pos(Qnil); warn_print("unexpected return\n"); break; case TAG_NEXT: - error_pos(); + error_pos(Qnil); warn_print("unexpected next\n"); break; case TAG_BREAK: - error_pos(); + error_pos(Qnil); warn_print("unexpected break\n"); break; case TAG_REDO: - error_pos(); + error_pos(Qnil); warn_print("unexpected redo\n"); break; case TAG_RETRY: - error_pos(); + error_pos(Qnil); warn_print("retry outside of rescue clause\n"); break; case TAG_THROW: /* TODO: fix me */ - error_pos(); + error_pos(Qnil); warn_print("unexpected throw\n"); break; case TAG_RAISE: { diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 3622cae0d9..da1cbd37cf 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -1094,4 +1094,15 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| } end; end + + def test_full_message + test_method = "def foo; raise 'testerror'; end" + + out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true) + assert(status1.success?) + assert(err1.empty?, "expected nothing wrote to $stdout by #long_message") + + _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) + assert_equal(err2, out1) + end end -- cgit v1.2.3