Permalink
Cannot retrieve contributors at this time
514 lines (469 sloc)
13.7 KB
/* -*-c-*- */ | |
/* | |
* included by eval.c | |
*/ | |
#define write_warn(str, x) \ | |
(NIL_P(str) ? warn_print(x) : (void)rb_str_cat_cstr(str, x)) | |
#define write_warn2(str, x, l) \ | |
(NIL_P(str) ? warn_print2(x, l) : (void)rb_str_cat(str, x, l)) | |
#ifdef HAVE_BUILTIN___BUILTIN_CONSTANT_P | |
#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 warn_print(x) rb_write_error(x) | |
#endif | |
#define warn_print2(x,l) rb_write_error2((x),(l)) | |
#define write_warn_str(str,x) NIL_P(str) ? rb_write_error_str(x) : (void)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(const VALUE str) | |
{ | |
VALUE pos = error_pos_str(); | |
if (!NIL_P(pos)) { | |
write_warn_str(str, pos); | |
} | |
} | |
static VALUE | |
error_pos_str(void) | |
{ | |
int sourceline; | |
VALUE sourcefile = rb_source_location(&sourceline); | |
if (!NIL_P(sourcefile)) { | |
ID caller_name; | |
if (sourceline == 0) { | |
return rb_sprintf("%"PRIsVALUE": ", sourcefile); | |
} | |
else if ((caller_name = rb_frame_callee()) != 0) { | |
return rb_sprintf("%"PRIsVALUE":%d:in `%"PRIsVALUE"': ", | |
sourcefile, sourceline, | |
rb_id2str(caller_name)); | |
} | |
else { | |
return rb_sprintf("%"PRIsVALUE":%d: ", sourcefile, sourceline); | |
} | |
} | |
return Qnil; | |
} | |
static void | |
set_backtrace(VALUE info, VALUE bt) | |
{ | |
ID set_backtrace = rb_intern("set_backtrace"); | |
if (rb_backtrace_p(bt)) { | |
if (rb_method_basic_definition_p(CLASS_OF(info), set_backtrace)) { | |
rb_exc_set_backtrace(info, bt); | |
return; | |
} | |
else { | |
bt = rb_backtrace_to_str_ary(bt); | |
} | |
} | |
rb_check_funcall(info, set_backtrace, 1, &bt); | |
} | |
static void | |
error_print(rb_execution_context_t *ec) | |
{ | |
rb_ec_error_print(ec, ec->errinfo); | |
} | |
static void | |
write_warnq(VALUE out, VALUE str, const char *ptr, long len) | |
{ | |
if (NIL_P(out)) { | |
const char *beg = ptr; | |
const long olen = len; | |
for (; len > 0; --len, ++ptr) { | |
unsigned char c = *ptr; | |
switch (c) { | |
case '\n': case '\t': continue; | |
} | |
if (rb_iscntrl(c)) { | |
char buf[5]; | |
const char *cc = 0; | |
if (ptr > beg) rb_write_error2(beg, ptr - beg); | |
beg = ptr + 1; | |
cc = ruby_escaped_char(c); | |
if (cc) { | |
rb_write_error2(cc, strlen(cc)); | |
} | |
else { | |
rb_write_error2(buf, snprintf(buf, sizeof(buf), "\\x%02X", c)); | |
} | |
} | |
else if (c == '\\') { | |
rb_write_error2(beg, ptr - beg + 1); | |
beg = ptr; | |
} | |
} | |
if (ptr > beg) { | |
if (beg == RSTRING_PTR(str) && olen == RSTRING_LEN(str)) | |
rb_write_error_str(str); | |
else | |
rb_write_error2(beg, ptr - beg); | |
} | |
} | |
else { | |
rb_str_cat(out, ptr, len); | |
} | |
} | |
#define CSI_BEGIN "\033[" | |
#define CSI_SGR "m" | |
static const char underline[] = CSI_BEGIN"1;4"CSI_SGR; | |
static const char bold[] = CSI_BEGIN"1"CSI_SGR; | |
static const char reset[] = CSI_BEGIN""CSI_SGR; | |
static void | |
print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, const VALUE str, int highlight) | |
{ | |
const char *einfo = ""; | |
long elen = 0; | |
VALUE mesg; | |
if (emesg != Qundef) { | |
if (NIL_P(errat) || RARRAY_LEN(errat) == 0 || | |
NIL_P(mesg = RARRAY_AREF(errat, 0))) { | |
error_pos(str); | |
} | |
else { | |
write_warn_str(str, mesg); | |
write_warn(str, ": "); | |
} | |
if (!NIL_P(emesg)) { | |
einfo = RSTRING_PTR(emesg); | |
elen = RSTRING_LEN(emesg); | |
} | |
} | |
if (eclass == rb_eRuntimeError && elen == 0) { | |
if (highlight) write_warn(str, underline); | |
write_warn(str, "unhandled exception"); | |
if (highlight) write_warn(str, reset); | |
write_warn2(str, "\n", 1); | |
} | |
else { | |
VALUE epath; | |
epath = rb_class_name(eclass); | |
if (elen == 0) { | |
if (highlight) write_warn(str, underline); | |
write_warn_str(str, epath); | |
if (highlight) write_warn(str, reset); | |
write_warn(str, "\n"); | |
} | |
else { | |
/* emesg is a String instance */ | |
const char *tail = 0; | |
if (highlight) write_warn(str, bold); | |
if (RSTRING_PTR(epath)[0] == '#') | |
epath = 0; | |
if ((tail = memchr(einfo, '\n', elen)) != 0) { | |
write_warnq(str, emesg, einfo, tail - einfo); | |
tail++; /* skip newline */ | |
} | |
else { | |
write_warnq(str, emesg, einfo, elen); | |
} | |
if (epath) { | |
write_warn(str, " ("); | |
if (highlight) write_warn(str, underline); | |
write_warn_str(str, epath); | |
if (highlight) { | |
write_warn(str, reset); | |
write_warn(str, bold); | |
} | |
write_warn2(str, ")", 1); | |
if (highlight) write_warn(str, reset); | |
write_warn2(str, "\n", 1); | |
} | |
if (tail && einfo+elen > tail) { | |
if (!highlight) { | |
write_warnq(str, emesg, tail, einfo+elen-tail); | |
if (einfo[elen-1] != '\n') write_warn2(str, "\n", 1); | |
} | |
else { | |
elen -= tail - einfo; | |
einfo = tail; | |
while (elen > 0) { | |
tail = memchr(einfo, '\n', elen); | |
if (!tail || tail > einfo) { | |
write_warn(str, bold); | |
write_warnq(str, emesg, einfo, tail ? tail-einfo : elen); | |
write_warn(str, reset); | |
if (!tail) { | |
write_warn2(str, "\n", 1); | |
break; | |
} | |
} | |
elen -= tail - einfo; | |
einfo = tail; | |
do ++tail; while (tail < einfo+elen && *tail == '\n'); | |
write_warnq(str, emesg, einfo, tail-einfo); | |
elen -= tail - einfo; | |
einfo = tail; | |
} | |
} | |
} | |
else if (!epath) { | |
write_warn2(str, "\n", 1); | |
} | |
} | |
} | |
} | |
static void | |
print_backtrace(const VALUE eclass, const VALUE errat, const VALUE str, int reverse, long backtrace_limit) | |
{ | |
if (!NIL_P(errat)) { | |
long i; | |
long len = RARRAY_LEN(errat); | |
const int threshold = 1000000000; | |
int width = (len <= 1) ? INT_MIN : ((int)log10((double)(len > threshold ? | |
((len - 1) / threshold) : | |
len - 1)) + | |
(len < threshold ? 0 : 9) + 1); | |
long skip_start = -1, skip_len = 0; | |
// skip for stackoverflow | |
if (eclass == rb_eSysStackError) { | |
long trace_head = 9; | |
long trace_tail = 4; | |
long trace_max = trace_head + trace_tail + 5; | |
if (len > trace_max) { | |
skip_start = trace_head; | |
skip_len = len - trace_max + 5; | |
} | |
} | |
// skip for explicit limit | |
if (backtrace_limit >= 0 && len > backtrace_limit + 2) { | |
skip_start = backtrace_limit + 1; | |
skip_len = len - skip_start; | |
} | |
for (i = 1; i < len; i++) { | |
if (i == skip_start) { | |
write_warn_str(str, rb_sprintf("\t ... %ld levels...\n", skip_len)); | |
i += skip_len; | |
if (i >= len) break; | |
} | |
VALUE line = RARRAY_AREF(errat, reverse ? len - i : i); | |
if (RB_TYPE_P(line, T_STRING)) { | |
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)); | |
} | |
} | |
} | |
} | |
VALUE rb_get_message(VALUE exc); | |
static int | |
shown_cause_p(VALUE cause, VALUE *shown_causes) | |
{ | |
VALUE shown = *shown_causes; | |
if (!shown) { | |
*shown_causes = shown = rb_obj_hide(rb_ident_hash_new()); | |
} | |
if (rb_hash_has_key(shown, cause)) return TRUE; | |
rb_hash_aset(shown, cause, Qtrue); | |
return FALSE; | |
} | |
static void | |
show_cause(VALUE errinfo, VALUE str, VALUE highlight, VALUE reverse, long backtrace_limit, VALUE *shown_causes) | |
{ | |
VALUE cause = rb_attr_get(errinfo, id_cause); | |
if (!NIL_P(cause) && rb_obj_is_kind_of(cause, rb_eException) && | |
!shown_cause_p(cause, shown_causes)) { | |
volatile VALUE eclass = CLASS_OF(cause); | |
VALUE errat = rb_get_backtrace(cause); | |
VALUE emesg = rb_get_message(cause); | |
if (reverse) { | |
show_cause(cause, str, highlight, reverse, backtrace_limit, shown_causes); | |
print_backtrace(eclass, errat, str, TRUE, backtrace_limit); | |
print_errinfo(eclass, errat, emesg, str, highlight!=0); | |
} | |
else { | |
print_errinfo(eclass, errat, emesg, str, highlight!=0); | |
print_backtrace(eclass, errat, str, FALSE, backtrace_limit); | |
show_cause(cause, str, highlight, reverse, backtrace_limit, shown_causes); | |
} | |
} | |
} | |
void | |
rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE highlight, VALUE reverse) | |
{ | |
volatile VALUE eclass; | |
VALUE shown_causes = 0; | |
long backtrace_limit = rb_backtrace_length_limit; | |
if (NIL_P(errinfo)) | |
return; | |
if (errat == Qundef) { | |
errat = Qnil; | |
} | |
eclass = CLASS_OF(errinfo); | |
if (NIL_P(reverse)) reverse = Qfalse; | |
if (NIL_P(highlight)) { | |
VALUE tty = (VALUE)rb_stderr_tty_p(); | |
if (NIL_P(highlight)) highlight = tty; | |
} | |
if (reverse) { | |
static const char traceback[] = "Traceback " | |
"(most recent call last):\n"; | |
const int bold_part = rb_strlen_lit("Traceback"); | |
char buff[sizeof(traceback)+sizeof(bold)+sizeof(reset)-2], *p = buff; | |
const char *msg = traceback; | |
long len = sizeof(traceback) - 1; | |
if (highlight) { | |
#define APPEND(s, l) (memcpy(p, s, l), p += (l)) | |
APPEND(bold, sizeof(bold)-1); | |
APPEND(traceback, bold_part); | |
APPEND(reset, sizeof(reset)-1); | |
APPEND(traceback + bold_part, sizeof(traceback)-bold_part-1); | |
#undef APPEND | |
len = p - (msg = buff); | |
} | |
write_warn2(str, msg, len); | |
show_cause(errinfo, str, highlight, reverse, backtrace_limit, &shown_causes); | |
print_backtrace(eclass, errat, str, TRUE, backtrace_limit); | |
print_errinfo(eclass, errat, emesg, str, highlight!=0); | |
} | |
else { | |
print_errinfo(eclass, errat, emesg, str, highlight!=0); | |
print_backtrace(eclass, errat, str, FALSE, backtrace_limit); | |
show_cause(errinfo, str, highlight, reverse, backtrace_limit, &shown_causes); | |
} | |
} | |
void | |
rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) | |
{ | |
volatile uint8_t raised_flag = ec->raised_flag; | |
volatile VALUE errat = Qundef; | |
volatile VALUE emesg = Qundef; | |
volatile bool written = false; | |
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); | |
} | |
if (emesg == Qundef) { | |
emesg = Qnil; | |
emesg = rb_get_message(errinfo); | |
} | |
if (!written) { | |
written = true; | |
rb_error_write(errinfo, emesg, errat, Qnil, Qnil, Qfalse); | |
} | |
EC_POP_TAG(); | |
ec->errinfo = errinfo; | |
rb_ec_raised_set(ec, raised_flag); | |
} | |
#define undef_mesg_for(v, k) rb_fstring_lit("undefined"v" method `%1$s' for "k" `%2$s'") | |
#define undef_mesg(v) ( \ | |
is_mod ? \ | |
undef_mesg_for(v, "module") : \ | |
undef_mesg_for(v, "class")) | |
void | |
rb_print_undef(VALUE klass, ID id, rb_method_visibility_t visi) | |
{ | |
const int is_mod = RB_TYPE_P(klass, T_MODULE); | |
VALUE mesg; | |
switch (visi & METHOD_VISI_MASK) { | |
case METHOD_VISI_UNDEF: | |
case METHOD_VISI_PUBLIC: mesg = undef_mesg(""); break; | |
case METHOD_VISI_PRIVATE: mesg = undef_mesg(" private"); break; | |
case METHOD_VISI_PROTECTED: mesg = undef_mesg(" protected"); break; | |
default: UNREACHABLE; | |
} | |
rb_name_err_raise_str(mesg, klass, ID2SYM(id)); | |
} | |
void | |
rb_print_undef_str(VALUE klass, VALUE name) | |
{ | |
const int is_mod = RB_TYPE_P(klass, T_MODULE); | |
rb_name_err_raise_str(undef_mesg(""), klass, name); | |
} | |
#define inaccessible_mesg_for(v, k) rb_fstring_lit("method `%1$s' for "k" `%2$s' is "v) | |
#define inaccessible_mesg(v) ( \ | |
is_mod ? \ | |
inaccessible_mesg_for(v, "module") : \ | |
inaccessible_mesg_for(v, "class")) | |
void | |
rb_print_inaccessible(VALUE klass, ID id, rb_method_visibility_t visi) | |
{ | |
const int is_mod = RB_TYPE_P(klass, T_MODULE); | |
VALUE mesg; | |
switch (visi & METHOD_VISI_MASK) { | |
case METHOD_VISI_UNDEF: | |
case METHOD_VISI_PUBLIC: mesg = inaccessible_mesg(""); break; | |
case METHOD_VISI_PRIVATE: mesg = inaccessible_mesg(" private"); break; | |
case METHOD_VISI_PROTECTED: mesg = inaccessible_mesg(" protected"); break; | |
default: UNREACHABLE; | |
} | |
rb_name_err_raise_str(mesg, klass, ID2SYM(id)); | |
} | |
static int | |
sysexit_status(VALUE err) | |
{ | |
VALUE st = rb_ivar_get(err, id_status); | |
return NUM2INT(st); | |
} | |
#define unknown_longjmp_status(status) \ | |
rb_bug("Unknown longjmp status %d", status) | |
static int | |
error_handle(rb_execution_context_t *ec, int ex) | |
{ | |
int status = EXIT_FAILURE; | |
if (rb_ec_set_raised(ec)) | |
return EXIT_FAILURE; | |
switch (ex & TAG_MASK) { | |
case 0: | |
status = EXIT_SUCCESS; | |
break; | |
case TAG_RETURN: | |
error_pos(Qnil); | |
warn_print("unexpected return\n"); | |
break; | |
case TAG_NEXT: | |
error_pos(Qnil); | |
warn_print("unexpected next\n"); | |
break; | |
case TAG_BREAK: | |
error_pos(Qnil); | |
warn_print("unexpected break\n"); | |
break; | |
case TAG_REDO: | |
error_pos(Qnil); | |
warn_print("unexpected redo\n"); | |
break; | |
case TAG_RETRY: | |
error_pos(Qnil); | |
warn_print("retry outside of rescue clause\n"); | |
break; | |
case TAG_THROW: | |
/* TODO: fix me */ | |
error_pos(Qnil); | |
warn_print("unexpected throw\n"); | |
break; | |
case TAG_RAISE: { | |
VALUE errinfo = ec->errinfo; | |
if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { | |
status = sysexit_status(errinfo); | |
} | |
else if (rb_obj_is_instance_of(errinfo, rb_eSignal) && | |
rb_ivar_get(errinfo, id_signo) != INT2FIX(SIGSEGV)) { | |
/* no message when exiting by signal */ | |
} | |
else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) && | |
FIXNUM_P(rb_attr_get(errinfo, id_signo))) { | |
/* no message when exiting by error to be mapped to signal */ | |
} | |
else { | |
rb_ec_error_print(ec, errinfo); | |
} | |
break; | |
} | |
case TAG_FATAL: | |
error_print(ec); | |
break; | |
default: | |
unknown_longjmp_status(ex); | |
break; | |
} | |
rb_ec_reset_raised(ec); | |
return status; | |
} |