Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ruby/eval_error.c
Line
Count
Source
1
/* -*-c-*- */
2
/*
3
 * included by eval.c
4
 */
5
6
#define write_warn(str, x) \
7
0
    (NIL_P(str) ? warn_print(x) : (void)rb_str_cat_cstr(str, x))
8
#define write_warn2(str, x, l) \
9
0
    (NIL_P(str) ? warn_print2(x, l) : (void)rb_str_cat(str, x, l))
10
#define write_warn_enc(str, x, l, enc) \
11
0
    (NIL_P(str) ? warn_print2(x, l) : (void)rb_enc_str_buf_cat(str, x, l, enc))
12
#ifdef HAVE_BUILTIN___BUILTIN_CONSTANT_P
13
0
#define warn_print(x) RB_GNUC_EXTENSION_BLOCK( \
14
0
    (__builtin_constant_p(x)) ?     \
15
0
        rb_write_error2((x), (long)strlen(x)) : \
16
0
        rb_write_error(x)     \
17
0
)
18
#else
19
#define warn_print(x) rb_write_error(x)
20
#endif
21
22
0
#define warn_print2(x,l) rb_write_error2((x),(l))
23
24
0
#define write_warn_str(str,x) NIL_P(str) ? rb_write_error_str(x) : (void)rb_str_concat((str), (x))
25
0
#define warn_print_str(x) rb_write_error_str(x)
26
27
static VALUE error_pos_str(void);
28
29
static void
30
error_pos(const VALUE str)
31
0
{
32
0
    VALUE pos = error_pos_str();
33
0
    if (!NIL_P(pos)) {
34
0
        write_warn_str(str, pos);
35
0
    }
36
0
}
37
38
static VALUE
39
error_pos_str(void)
40
0
{
41
0
    int sourceline;
42
0
    VALUE sourcefile = rb_source_location(&sourceline);
43
44
0
    if (!NIL_P(sourcefile)) {
45
0
        ID caller_name;
46
0
        if (sourceline == 0) {
47
0
            return rb_sprintf("%"PRIsVALUE": ", sourcefile);
48
0
        }
49
0
        else if ((caller_name = rb_frame_callee()) != 0) {
50
0
            return rb_sprintf("%"PRIsVALUE":%d:in '%"PRIsVALUE"': ",
51
0
                              sourcefile, sourceline,
52
0
                              rb_id2str(caller_name));
53
0
        }
54
0
        else {
55
0
            return rb_sprintf("%"PRIsVALUE":%d: ", sourcefile, sourceline);
56
0
        }
57
0
    }
58
0
    return Qnil;
59
0
}
60
61
static void
62
set_backtrace(VALUE info, VALUE bt)
63
19.0k
{
64
19.0k
    ID set_backtrace = rb_intern("set_backtrace");
65
66
19.0k
    if (rb_backtrace_p(bt)) {
67
18.8k
        if (rb_method_basic_definition_p(CLASS_OF(info), set_backtrace)) {
68
18.8k
            rb_exc_set_backtrace(info, bt);
69
18.8k
            return;
70
18.8k
        }
71
0
        else {
72
0
            bt = rb_backtrace_to_str_ary(bt);
73
0
        }
74
18.8k
    }
75
191
    rb_check_funcall(info, set_backtrace, 1, &bt);
76
191
}
77
78
#define CSI_BEGIN "\033["
79
#define CSI_SGR "m"
80
81
static const char underline[] = CSI_BEGIN"1;4"CSI_SGR;
82
static const char bold[] = CSI_BEGIN"1"CSI_SGR;
83
static const char reset[] = CSI_BEGIN""CSI_SGR;
84
85
static void
86
print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, const VALUE str, int highlight)
87
0
{
88
0
    long elen = 0;
89
0
    VALUE mesg;
90
91
0
    if (NIL_P(errat) || RARRAY_LEN(errat) == 0 ||
92
0
        NIL_P(mesg = RARRAY_AREF(errat, 0))) {
93
0
        error_pos(str);
94
0
    }
95
0
    else {
96
0
        write_warn_str(str, mesg);
97
0
        write_warn(str, ": ");
98
0
    }
99
100
0
    if (!NIL_P(emesg)) {
101
0
        elen = RSTRING_LEN(emesg);
102
0
    }
103
104
0
    if (eclass == rb_eRuntimeError && elen == 0) {
105
0
        if (highlight) write_warn(str, underline);
106
0
        write_warn(str, "unhandled exception");
107
0
        if (highlight) write_warn(str, reset);
108
0
        write_warn(str, "\n");
109
0
    }
110
0
    else {
111
0
        VALUE epath;
112
113
0
        epath = rb_class_name(eclass);
114
0
        if (elen == 0) {
115
0
            if (highlight) write_warn(str, underline);
116
0
            write_warn_str(str, epath);
117
0
            if (highlight) write_warn(str, reset);
118
0
            write_warn(str, "\n");
119
0
        }
120
0
        else {
121
0
            write_warn_str(str, emesg);
122
0
            write_warn(str, "\n");
123
0
        }
124
0
    }
125
0
}
126
127
VALUE
128
rb_decorate_message(const VALUE eclass, VALUE emesg, int highlight)
129
0
{
130
0
    const char *einfo = "";
131
0
    long elen = 0;
132
0
    rb_encoding *eenc;
133
134
0
    VALUE str = rb_usascii_str_new_cstr("");
135
136
0
    if (!NIL_P(emesg) && rb_enc_asciicompat(eenc = rb_enc_get(emesg))) {
137
0
        einfo = RSTRING_PTR(emesg);
138
0
        elen = RSTRING_LEN(emesg);
139
0
    }
140
0
    else {
141
0
        eenc = NULL;
142
0
    }
143
0
    if (eclass == rb_eRuntimeError && elen == 0) {
144
0
        if (highlight) write_warn(str, underline);
145
0
        write_warn(str, "unhandled exception");
146
0
        if (highlight) write_warn(str, reset);
147
0
    }
148
0
    else {
149
0
        VALUE epath;
150
151
0
        epath = rb_class_name(eclass);
152
0
        if (elen == 0) {
153
0
            if (highlight) write_warn(str, underline);
154
0
            write_warn_str(str, epath);
155
0
            if (highlight) write_warn(str, reset);
156
0
        }
157
0
        else {
158
            /* emesg is a String instance */
159
0
            const char *tail = 0;
160
161
0
            if (highlight) write_warn(str, bold);
162
0
            if (RSTRING_PTR(epath)[0] == '#')
163
0
                epath = 0;
164
0
            if ((tail = memchr(einfo, '\n', elen)) != 0) {
165
0
                write_warn_enc(str, einfo, tail - einfo, eenc);
166
0
                tail++;   /* skip newline */
167
0
            }
168
0
            else {
169
0
                write_warn_str(str, emesg);
170
0
            }
171
0
            if (epath) {
172
0
                write_warn(str, " (");
173
0
                if (highlight) write_warn(str, underline);
174
0
                write_warn_str(str, epath);
175
0
                if (highlight) {
176
0
                    write_warn(str, reset);
177
0
                    write_warn(str, bold);
178
0
                }
179
0
                write_warn(str, ")");
180
0
                if (highlight) write_warn(str, reset);
181
0
            }
182
0
            if (tail && einfo+elen > tail) {
183
0
                if (!highlight) {
184
0
                    write_warn(str, "\n");
185
0
                    write_warn_enc(str, tail, einfo+elen-tail, eenc);
186
0
                }
187
0
                else {
188
0
                    elen -= tail - einfo;
189
0
                    einfo = tail;
190
0
                    write_warn(str, "\n");
191
0
                    while (elen > 0) {
192
0
                        tail = memchr(einfo, '\n', elen);
193
0
                        if (!tail || tail > einfo) {
194
0
                            write_warn(str, bold);
195
0
                            write_warn_enc(str, einfo, tail ? tail-einfo : elen, eenc);
196
0
                            write_warn(str, reset);
197
0
                            if (!tail) {
198
0
                                break;
199
0
                            }
200
0
                        }
201
0
                        elen -= tail - einfo;
202
0
                        einfo = tail;
203
0
                        do ++tail; while (tail < einfo+elen && *tail == '\n');
204
0
                        write_warn_enc(str, einfo, tail-einfo, eenc);
205
0
                        elen -= tail - einfo;
206
0
                        einfo = tail;
207
0
                    }
208
0
                }
209
0
            }
210
0
        }
211
0
    }
212
213
0
    RB_GC_GUARD(emesg);
214
215
0
    return str;
216
0
}
217
218
static void
219
print_backtrace(const VALUE eclass, const VALUE errat, const VALUE str, int reverse, long backtrace_limit)
220
0
{
221
0
    if (!NIL_P(errat)) {
222
0
        long i;
223
0
        long len = RARRAY_LEN(errat);
224
0
        const int threshold = 1000000000;
225
0
        int width = (len <= 1) ? INT_MIN : ((int)log10((double)(len > threshold ?
226
0
                                         ((len - 1) / threshold) :
227
0
                                         len - 1)) +
228
0
                     (len < threshold ? 0 : 9) + 1);
229
230
0
        long skip_start = -1, skip_len = 0;
231
232
        // skip for stackoverflow
233
0
        if (eclass == rb_eSysStackError) {
234
0
            long trace_head = 9;
235
0
            long trace_tail = 4;
236
0
            long trace_max = trace_head + trace_tail + 5;
237
0
            if (len > trace_max) {
238
0
                skip_start = trace_head;
239
0
                skip_len = len - trace_max + 5;
240
0
            }
241
0
        }
242
243
        // skip for explicit limit
244
0
        if (backtrace_limit >= 0 && len > backtrace_limit + 2) {
245
0
            skip_start = backtrace_limit + 1;
246
0
            skip_len = len - skip_start;
247
0
        }
248
249
0
        for (i = 1; i < len; i++) {
250
0
            if (i == skip_start) {
251
0
                write_warn_str(str, rb_sprintf("\t ... %ld levels...\n", skip_len));
252
0
                i += skip_len;
253
0
                if (i >= len) break;
254
0
            }
255
0
            VALUE line = RARRAY_AREF(errat, reverse ? len - i : i);
256
0
            if (RB_TYPE_P(line, T_STRING)) {
257
0
                VALUE bt = rb_str_new_cstr("\t");
258
0
                if (reverse) rb_str_catf(bt, "%*ld: ", width, len - i);
259
0
                write_warn_str(str, rb_str_catf(bt, "from %"PRIsVALUE"\n", line));
260
0
            }
261
0
        }
262
0
    }
263
0
}
264
265
VALUE rb_get_detailed_message(VALUE exc, VALUE opt);
266
267
static int
268
shown_cause_p(VALUE cause, VALUE *shown_causes)
269
0
{
270
0
    VALUE shown = *shown_causes;
271
0
    if (!shown) {
272
0
        *shown_causes = shown = rb_obj_hide(rb_ident_hash_new());
273
0
    }
274
0
    if (rb_hash_has_key(shown, cause)) return TRUE;
275
0
    rb_hash_aset(shown, cause, Qtrue);
276
0
    return FALSE;
277
0
}
278
279
static void
280
show_cause(VALUE errinfo, VALUE str, VALUE opt, VALUE highlight, VALUE reverse, long backtrace_limit, VALUE *shown_causes)
281
0
{
282
0
    VALUE cause = rb_attr_get(errinfo, id_cause);
283
0
    if (!NIL_P(cause) && rb_obj_is_kind_of(cause, rb_eException) &&
284
0
        !shown_cause_p(cause, shown_causes)) {
285
0
        volatile VALUE eclass = CLASS_OF(cause);
286
0
        VALUE errat = rb_get_backtrace(cause);
287
0
        VALUE emesg = rb_get_detailed_message(cause, opt);
288
0
        if (reverse) {
289
0
            show_cause(cause, str, opt, highlight, reverse, backtrace_limit, shown_causes);
290
0
            print_backtrace(eclass, errat, str, TRUE, backtrace_limit);
291
0
            print_errinfo(eclass, errat, emesg, str, RTEST(highlight));
292
0
        }
293
0
        else {
294
0
            print_errinfo(eclass, errat, emesg, str, RTEST(highlight));
295
0
            print_backtrace(eclass, errat, str, FALSE, backtrace_limit);
296
0
            show_cause(cause, str, opt, highlight, reverse, backtrace_limit, shown_causes);
297
0
        }
298
0
    }
299
0
}
300
301
void
302
rb_exc_check_circular_cause(VALUE exc)
303
0
{
304
0
    VALUE cause = exc, shown_causes = 0;
305
0
    do {
306
0
        if (shown_cause_p(cause, &shown_causes)) {
307
0
            rb_raise(rb_eArgError, "circular causes");
308
0
        }
309
0
    } while (!NIL_P(cause = rb_attr_get(cause, id_cause)));
310
0
}
311
312
void
313
rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse)
314
0
{
315
0
    volatile VALUE eclass;
316
0
    VALUE shown_causes = 0;
317
0
    long backtrace_limit = rb_backtrace_length_limit;
318
319
0
    if (NIL_P(errinfo))
320
0
        return;
321
322
0
    if (UNDEF_P(errat)) {
323
0
        errat = Qnil;
324
0
    }
325
0
    eclass = CLASS_OF(errinfo);
326
0
    if (reverse) {
327
0
        static const char traceback[] = "Traceback "
328
0
            "(most recent call last):\n";
329
0
        const int bold_part = rb_strlen_lit("Traceback");
330
0
        char buff[sizeof(traceback)+sizeof(bold)+sizeof(reset)-2], *p = buff;
331
0
        const char *msg = traceback;
332
0
        long len = sizeof(traceback) - 1;
333
0
        if (RTEST(highlight)) {
334
0
#define APPEND(s, l) (memcpy(p, s, l), p += (l))
335
0
            APPEND(bold, sizeof(bold)-1);
336
0
            APPEND(traceback, bold_part);
337
0
            APPEND(reset, sizeof(reset)-1);
338
0
            APPEND(traceback + bold_part, sizeof(traceback)-bold_part-1);
339
0
#undef APPEND
340
0
            len = p - (msg = buff);
341
0
        }
342
0
        write_warn2(str, msg, len);
343
0
        show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, &shown_causes);
344
0
        print_backtrace(eclass, errat, str, TRUE, backtrace_limit);
345
0
        print_errinfo(eclass, errat, emesg, str, RTEST(highlight));
346
0
    }
347
0
    else {
348
0
        print_errinfo(eclass, errat, emesg, str, RTEST(highlight));
349
0
        print_backtrace(eclass, errat, str, FALSE, backtrace_limit);
350
0
        show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, &shown_causes);
351
0
    }
352
0
}
353
354
static void
355
rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo, const VALUE str, VALUE emesg0)
356
0
{
357
0
    volatile uint8_t raised_flag = ec->raised_flag;
358
0
    volatile VALUE errat = Qundef;
359
0
    volatile bool written = false;
360
0
    volatile VALUE emesg = emesg0;
361
362
0
    VALUE opt = rb_hash_new();
363
0
    VALUE highlight = rb_stderr_tty_p() ? Qtrue : Qfalse;
364
0
    rb_hash_aset(opt, ID2SYM(rb_intern_const("highlight")), highlight);
365
366
0
    if (NIL_P(errinfo))
367
0
        return;
368
0
    rb_ec_raised_clear(ec);
369
370
0
    EC_PUSH_TAG(ec);
371
0
    if (EC_EXEC_TAG() == TAG_NONE) {
372
0
        errat = rb_get_backtrace(errinfo);
373
0
    }
374
0
    if (UNDEF_P(emesg)) {
375
0
        emesg = Qnil;
376
0
        emesg = rb_get_detailed_message(errinfo, opt);
377
0
    }
378
379
0
    if (!written) {
380
0
        written = true;
381
0
        rb_error_write(errinfo, emesg, errat, str, opt, highlight, Qfalse);
382
0
    }
383
384
0
    EC_POP_TAG();
385
0
    ec->errinfo = errinfo;
386
0
    rb_ec_raised_set(ec, raised_flag);
387
0
}
388
389
void
390
rb_ec_error_print(rb_execution_context_t *volatile ec, volatile VALUE errinfo)
391
0
{
392
0
    rb_ec_error_print_detailed(ec, errinfo, Qnil, Qundef);
393
0
}
394
395
0
#define undef_mesg_for(v, k) rb_fstring_lit("undefined"v" method '%1$s' for "k" '%2$s'")
396
0
#define undef_mesg(v) ( \
397
0
        is_mod ? \
398
0
        undef_mesg_for(v, "module") : \
399
0
        undef_mesg_for(v, "class"))
400
401
void
402
rb_print_undef(VALUE klass, ID id, rb_method_visibility_t visi)
403
0
{
404
0
    const int is_mod = RB_TYPE_P(klass, T_MODULE);
405
0
    VALUE mesg;
406
0
    switch (visi & METHOD_VISI_MASK) {
407
0
      case METHOD_VISI_UNDEF:
408
0
      case METHOD_VISI_PUBLIC:    mesg = undef_mesg(""); break;
409
0
      case METHOD_VISI_PRIVATE:   mesg = undef_mesg(" private"); break;
410
0
      case METHOD_VISI_PROTECTED: mesg = undef_mesg(" protected"); break;
411
0
      default: UNREACHABLE;
412
0
    }
413
0
    rb_name_err_raise_str(mesg, klass, ID2SYM(id));
414
0
}
415
416
void
417
rb_print_undef_str(VALUE klass, VALUE name)
418
0
{
419
0
    const int is_mod = RB_TYPE_P(klass, T_MODULE);
420
0
    rb_name_err_raise_str(undef_mesg(""), klass, name);
421
0
}
422
423
0
#define inaccessible_mesg_for(v, k) rb_fstring_lit("method '%1$s' for "k" '%2$s' is "v)
424
0
#define inaccessible_mesg(v) ( \
425
0
        is_mod ? \
426
0
        inaccessible_mesg_for(v, "module") : \
427
0
        inaccessible_mesg_for(v, "class"))
428
429
void
430
rb_print_inaccessible(VALUE klass, ID id, rb_method_visibility_t visi)
431
0
{
432
0
    const int is_mod = RB_TYPE_P(klass, T_MODULE);
433
0
    VALUE mesg;
434
0
    switch (visi & METHOD_VISI_MASK) {
435
0
      case METHOD_VISI_UNDEF:
436
0
      case METHOD_VISI_PUBLIC:    mesg = inaccessible_mesg(""); break;
437
0
      case METHOD_VISI_PRIVATE:   mesg = inaccessible_mesg("private"); break;
438
0
      case METHOD_VISI_PROTECTED: mesg = inaccessible_mesg("protected"); break;
439
0
      default: UNREACHABLE;
440
0
    }
441
0
    rb_name_err_raise_str(mesg, klass, ID2SYM(id));
442
0
}
443
444
static int
445
sysexit_status(VALUE err)
446
0
{
447
0
    VALUE st = rb_ivar_get(err, id_status);
448
0
    return NUM2INT(st);
449
0
}
450
451
enum {
452
    EXITING_WITH_MESSAGE = 1,
453
    EXITING_WITH_STATUS = 2,
454
    EXITING_WITH_SIGNAL = 4
455
};
456
static int
457
exiting_split(VALUE errinfo, volatile int *exitcode, volatile int *sigstatus)
458
0
{
459
0
    int ex = EXIT_SUCCESS;
460
0
    VALUE signo;
461
0
    int sig = 0;
462
0
    int result = 0;
463
464
0
    if (NIL_P(errinfo)) return 0;
465
466
0
    if (THROW_DATA_P(errinfo)) {
467
0
        int throw_state = ((const struct vm_throw_data *)errinfo)->throw_state;
468
0
        ex = throw_state & VM_THROW_STATE_MASK;
469
0
        result |= EXITING_WITH_STATUS;
470
0
    }
471
0
    else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) {
472
0
        ex = sysexit_status(errinfo);
473
0
        result |= EXITING_WITH_STATUS;
474
0
    }
475
0
    else if (rb_obj_is_kind_of(errinfo, rb_eSignal)) {
476
0
        signo = rb_ivar_get(errinfo, id_signo);
477
0
        sig = FIX2INT(signo);
478
0
        result |= EXITING_WITH_SIGNAL;
479
        /* no message when exiting by signal */
480
0
        if (signo == INT2FIX(SIGSEGV) || !rb_obj_is_instance_of(errinfo, rb_eSignal))
481
            /* except for SEGV and subclasses */
482
0
            result |= EXITING_WITH_MESSAGE;
483
0
    }
484
0
    else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) &&
485
0
        FIXNUM_P(signo = rb_attr_get(errinfo, id_signo))) {
486
0
        sig = FIX2INT(signo);
487
0
        result |= EXITING_WITH_SIGNAL;
488
        /* no message when exiting by error to be mapped to signal */
489
0
    }
490
0
    else {
491
0
        ex = EXIT_FAILURE;
492
0
        result |= EXITING_WITH_STATUS | EXITING_WITH_MESSAGE;
493
0
    }
494
495
0
    if (exitcode && (result & EXITING_WITH_STATUS))
496
0
        *exitcode = ex;
497
0
    if (sigstatus && (result & EXITING_WITH_SIGNAL))
498
0
        *sigstatus = sig;
499
500
0
    return result;
501
0
}
502
503
#define unknown_longjmp_status(status) \
504
0
    rb_bug("Unknown longjmp status %d", status)
505
506
static int
507
error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex)
508
0
{
509
0
    int status = EXIT_FAILURE;
510
511
0
    if (rb_ec_set_raised(ec))
512
0
        return EXIT_FAILURE;
513
0
    switch (ex & TAG_MASK) {
514
0
      case 0:
515
0
        status = EXIT_SUCCESS;
516
0
        break;
517
518
0
      case TAG_RETURN:
519
0
        error_pos(Qnil);
520
0
        warn_print("unexpected return\n");
521
0
        break;
522
0
      case TAG_NEXT:
523
0
        error_pos(Qnil);
524
0
        warn_print("unexpected next\n");
525
0
        break;
526
0
      case TAG_BREAK:
527
0
        error_pos(Qnil);
528
0
        warn_print("unexpected break\n");
529
0
        break;
530
0
      case TAG_REDO:
531
0
        error_pos(Qnil);
532
0
        warn_print("unexpected redo\n");
533
0
        break;
534
0
      case TAG_RETRY:
535
0
        error_pos(Qnil);
536
0
        warn_print("retry outside of rescue clause\n");
537
0
        break;
538
0
      case TAG_THROW:
539
        /* TODO: fix me */
540
0
        error_pos(Qnil);
541
0
        warn_print("unexpected throw\n");
542
0
        break;
543
0
      case TAG_RAISE:
544
0
        if (!(exiting_split(errinfo, &status, NULL) & EXITING_WITH_MESSAGE)) {
545
0
            break;
546
0
        }
547
        /* fallthrough */
548
0
      case TAG_FATAL:
549
0
        rb_ec_error_print(ec, errinfo);
550
0
        break;
551
0
      default:
552
0
        unknown_longjmp_status(ex);
553
0
        break;
554
0
    }
555
0
    rb_ec_reset_raised(ec);
556
0
    return status;
557
0
}