ruby/error.c
Hiroshi SHIBATA 22258b1d5f Load the error decoration gems from non-main Ractors too
rb_require_string delegates requires from non-main Ractors to the
main Ractor via rb_ractor_require, so the main-Ractor guard here was
unnecessary. Drop it and claim the load with an atomic exchange,
because Ractors run in parallel and the plain bool flag was only safe
under that guard. did_you_mean already keeps its state Ractor-safe,
so suggestions now appear in non-main Ractors as well.

Suggested by John Hawthorn.
2026-07-21 11:49:03 +09:00

4381 lines
114 KiB
C

/**********************************************************************
error.c -
$Author$
created at: Mon Aug 9 16:11:34 JST 1993
Copyright (C) 1993-2007 Yukihiro Matsumoto
**********************************************************************/
#include "ruby/internal/config.h"
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#if defined __APPLE__
# include <AvailabilityMacros.h>
#endif
#include "internal.h"
#include "internal/class.h"
#include "internal/error.h"
#include "internal/eval.h"
#include "internal/hash.h"
#include "internal/io.h"
#include "internal/load.h"
#include "internal/object.h"
#include "internal/process.h"
#include "internal/string.h"
#include "internal/symbol.h"
#include "internal/thread.h"
#include "internal/variable.h"
#include "ruby/encoding.h"
#include "ruby/st.h"
#include "ruby/util.h"
#include "internal/vm.h"
#include "ruby_assert.h"
#include "vm_core.h"
#include "yjit.h"
#include "zjit.h"
#include "builtin.h"
/*!
* \addtogroup exception
* \{
*/
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef WIFEXITED
#define WIFEXITED(status) 1
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(status) (status)
#endif
VALUE rb_iseqw_local_variables(VALUE iseqval);
VALUE rb_iseqw_new(const rb_iseq_t *);
int rb_str_end_with_asciichar(VALUE str, int c);
long rb_backtrace_length_limit = -1;
VALUE rb_eEAGAIN;
VALUE rb_eEWOULDBLOCK;
VALUE rb_eEINPROGRESS;
static VALUE rb_mWarning;
static VALUE rb_cWarningBuffer;
static ID id_warn;
static ID id_category;
static ID id_deprecated;
static ID id_experimental;
static ID id_performance;
static ID id_strict_unused_block;
static VALUE sym_category;
static VALUE sym_highlight;
static struct {
st_table *id2enum, *enum2id;
} warning_categories;
extern const char *rb_dynamic_description;
static const char *
rb_strerrno(int err)
{
#define defined_error(name, num) if (err == (num)) return (name);
#define undefined_error(name)
#include "known_errors.inc"
#undef defined_error
#undef undefined_error
return NULL;
}
static int
err_position_0(char *buf, long len, const char *file, int line)
{
if (!file) {
return 0;
}
else if (line == 0) {
return snprintf(buf, len, "%s: ", file);
}
else {
return snprintf(buf, len, "%s:%d: ", file, line);
}
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 5, 0)
static VALUE
err_vcatf(VALUE str, const char *pre, const char *file, int line,
const char *fmt, va_list args)
{
if (file) {
rb_str_cat2(str, file);
if (line) rb_str_catf(str, ":%d", line);
rb_str_cat2(str, ": ");
}
if (pre) rb_str_cat2(str, pre);
rb_str_vcatf(str, fmt, args);
return str;
}
static VALUE syntax_error_with_path(VALUE, VALUE, VALUE*, rb_encoding*);
VALUE
rb_syntax_error_append(VALUE exc, VALUE file, int line, int column,
rb_encoding *enc, const char *fmt, va_list args)
{
const char *fn = NIL_P(file) ? NULL : RSTRING_PTR(file);
if (!exc) {
VALUE mesg = rb_enc_str_new(0, 0, enc);
err_vcatf(mesg, NULL, fn, line, fmt, args);
rb_str_cat2(mesg, "\n");
rb_write_error_str(mesg);
}
else {
VALUE mesg;
exc = syntax_error_with_path(exc, file, &mesg, enc);
err_vcatf(mesg, NULL, fn, line, fmt, args);
}
return exc;
}
static unsigned int warning_disabled_categories = (
(1U << RB_WARN_CATEGORY_DEPRECATED) |
~RB_WARN_CATEGORY_DEFAULT_BITS);
static unsigned int
rb_warning_category_mask(VALUE category)
{
return 1U << rb_warning_category_from_name(category);
}
rb_warning_category_t
rb_warning_category_from_name(VALUE category)
{
st_data_t cat_value;
ID cat_id;
Check_Type(category, T_SYMBOL);
if (!(cat_id = rb_check_id(&category)) ||
!st_lookup(warning_categories.id2enum, cat_id, &cat_value)) {
rb_raise(rb_eArgError, "unknown category: %"PRIsVALUE, category);
}
return (rb_warning_category_t)cat_value;
}
static VALUE
rb_warning_category_to_name(rb_warning_category_t category)
{
st_data_t id;
if (!st_lookup(warning_categories.enum2id, category, &id)) {
rb_raise(rb_eArgError, "invalid category: %d", (int)category);
}
return id ? ID2SYM(id) : Qnil;
}
void
rb_warning_category_update(unsigned int mask, unsigned int bits)
{
warning_disabled_categories &= ~mask;
warning_disabled_categories |= mask & ~bits;
}
bool
rb_warning_category_enabled_p(rb_warning_category_t category)
{
return !(warning_disabled_categories & (1U << category));
}
/*
* call-seq:
* Warning[category] -> true or false
*
* Returns the flag to show the warning messages for +category+.
* Supported categories are:
*
* +:deprecated+ ::
* deprecation warnings
* * assignment of non-nil value to <code>$,</code> and <code>$;</code>
* * keyword arguments
* etc.
*
* +:experimental+ ::
* experimental features
*
* +:performance+ ::
* performance hints
* * Shape variation limit
*/
static VALUE
rb_warning_s_aref(VALUE mod, VALUE category)
{
rb_warning_category_t cat = rb_warning_category_from_name(category);
return RBOOL(rb_warning_category_enabled_p(cat));
}
/*
* call-seq:
* Warning[category] = flag -> flag
*
* Sets the warning flags for +category+.
* See Warning.[] for the categories.
*/
static VALUE
rb_warning_s_aset(VALUE mod, VALUE category, VALUE flag)
{
unsigned int mask = rb_warning_category_mask(category);
unsigned int disabled = warning_disabled_categories;
if (!RTEST(flag))
disabled |= mask;
else
disabled &= ~mask;
warning_disabled_categories = disabled;
return flag;
}
/*
* call-seq:
* categories -> array
*
* Returns a list of the supported category symbols.
*/
static VALUE
rb_warning_s_categories(VALUE mod)
{
st_index_t num = warning_categories.id2enum->num_entries;
ID *ids = ALLOCA_N(ID, num);
num = st_keys(warning_categories.id2enum, ids, num);
VALUE ary = rb_ary_new_capa(num);
for (st_index_t i = 0; i < num; ++i) {
rb_ary_push(ary, ID2SYM(ids[i]));
}
return rb_ary_freeze(ary);
}
/*
* call-seq:
* warn(msg, category: nil) -> nil
*
* Writes warning message +msg+ to $stderr. This method is called by
* Ruby for all emitted warnings. A +category+ may be included with
* the warning.
*
* See the documentation of the Warning module for how to customize this.
*/
static VALUE
rb_warning_s_warn(int argc, VALUE *argv, VALUE mod)
{
VALUE str;
VALUE opt;
VALUE category = Qnil;
rb_scan_args(argc, argv, "1:", &str, &opt);
if (!NIL_P(opt)) rb_get_kwargs(opt, &id_category, 0, 1, &category);
Check_Type(str, T_STRING);
rb_must_asciicompat(str);
if (!NIL_P(category)) {
rb_warning_category_t cat = rb_warning_category_from_name(category);
if (!rb_warning_category_enabled_p(cat)) return Qnil;
}
rb_write_error_str(str);
return Qnil;
}
/*
* Document-module: Warning
*
* The Warning module contains a single method named #warn, and the
* module extends itself, making Warning.warn available.
* Warning.warn is called for all warnings issued by Ruby.
* By default, warnings are printed to $stderr.
*
* Changing the behavior of Warning.warn is useful to customize how warnings are
* handled by Ruby, for instance by filtering some warnings, and/or outputting
* warnings somewhere other than <tt>$stderr</tt>.
*
* If you want to change the behavior of Warning.warn you should use
* <tt>Warning.extend(MyNewModuleWithWarnMethod)</tt> and you can use +super+
* to get the default behavior of printing the warning to <tt>$stderr</tt>.
*
* Example:
* module MyWarningFilter
* def warn(message, category: nil, **kwargs)
* if /some warning I want to ignore/.match?(message)
* # ignore
* else
* super
* end
* end
* end
* Warning.extend MyWarningFilter
*
* You should never redefine Warning#warn (the instance method), as that will
* then no longer provide a way to use the default behavior.
*
* The warning[https://rubygems.org/gems/warning] gem provides convenient ways to customize Warning.warn.
*/
static VALUE
rb_warning_warn(VALUE mod, VALUE str)
{
return rb_funcallv(mod, id_warn, 1, &str);
}
static int
rb_warning_warn_arity(void)
{
const rb_method_entry_t *me = rb_method_entry(rb_singleton_class(rb_mWarning), id_warn);
return me ? rb_method_entry_arity(me) : 1;
}
static VALUE
rb_warn_category(VALUE str, VALUE category)
{
if (RUBY_DEBUG && !NIL_P(category)) {
rb_warning_category_from_name(category);
}
if (rb_warning_warn_arity() == 1) {
return rb_warning_warn(rb_mWarning, str);
}
else {
VALUE args[2];
args[0] = str;
args[1] = rb_hash_new();
rb_hash_aset(args[1], sym_category, category);
return rb_funcallv_kw(rb_mWarning, id_warn, 2, args, RB_PASS_KEYWORDS);
}
}
static void
rb_write_warning_str(VALUE str)
{
rb_warning_warn(rb_mWarning, str);
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 4, 0)
static VALUE
warn_vsprintf(rb_encoding *enc, const char *file, int line, const char *fmt, va_list args)
{
VALUE str = rb_enc_str_new(0, 0, enc);
err_vcatf(str, "warning: ", file, line, fmt, args);
return rb_str_cat2(str, "\n");
}
#define with_warn_vsprintf(enc, file, line, fmt) \
VALUE str; \
va_list args; \
va_start(args, fmt); \
str = warn_vsprintf(enc, file, line, fmt, args); \
va_end(args);
void
rb_compile_warn(const char *file, int line, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warn_vsprintf(NULL, file, line, fmt) {
rb_write_warning_str(str);
}
}
}
void
rb_enc_compile_warn(rb_encoding *enc, const char *file, int line, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warn_vsprintf(enc, file, line, fmt) {
rb_write_warning_str(str);
}
}
}
/* rb_compile_warning() reports only in verbose mode */
void
rb_compile_warning(const char *file, int line, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warn_vsprintf(NULL, file, line, fmt) {
rb_write_warning_str(str);
}
}
}
/* rb_enc_compile_warning() reports only in verbose mode */
void
rb_enc_compile_warning(rb_encoding *enc, const char *file, int line, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warn_vsprintf(enc, file, line, fmt) {
rb_write_warning_str(str);
}
}
}
void
rb_category_compile_warn(rb_warning_category_t category, const char *file, int line, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warn_vsprintf(NULL, file, line, fmt) {
rb_warn_category(str, rb_warning_category_to_name(category));
}
}
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0)
static VALUE
warning_string(rb_encoding *enc, const char *fmt, va_list args)
{
int line;
const char *file = rb_source_location_cstr(&line);
return warn_vsprintf(enc, file, line, fmt, args);
}
#define with_warning_string(mesg, enc, fmt) \
with_warning_string_from(mesg, enc, fmt, fmt)
#define with_warning_string_from(mesg, enc, fmt, last_arg) \
VALUE mesg; \
va_list args; va_start(args, last_arg); \
mesg = warning_string(enc, fmt, args); \
va_end(args);
void
rb_warn(const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
rb_write_warning_str(mesg);
}
}
}
void
rb_category_warn(rb_warning_category_t category, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose) && rb_warning_category_enabled_p(category)) {
with_warning_string(mesg, 0, fmt) {
rb_warn_category(mesg, rb_warning_category_to_name(category));
}
}
}
void
rb_enc_warn(rb_encoding *enc, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warning_string(mesg, enc, fmt) {
rb_write_warning_str(mesg);
}
}
}
/* rb_warning() reports only in verbose mode */
void
rb_warning(const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
rb_write_warning_str(mesg);
}
}
}
/* rb_category_warning() reports only in verbose mode */
void
rb_category_warning(rb_warning_category_t category, const char *fmt, ...)
{
if (RTEST(ruby_verbose) && rb_warning_category_enabled_p(category)) {
with_warning_string(mesg, 0, fmt) {
rb_warn_category(mesg, rb_warning_category_to_name(category));
}
}
}
VALUE
rb_warning_string(const char *fmt, ...)
{
with_warning_string(mesg, 0, fmt) {
}
return mesg;
}
#if 0
void
rb_enc_warning(rb_encoding *enc, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warning_string(mesg, enc, fmt) {
rb_write_warning_str(mesg);
}
}
}
#endif
static bool
deprecation_warning_enabled(void)
{
if (NIL_P(ruby_verbose)) return false;
if (!rb_warning_category_enabled_p(RB_WARN_CATEGORY_DEPRECATED)) return false;
return true;
}
static void
warn_deprecated(VALUE mesg, const char *removal, const char *suggest)
{
rb_str_set_len(mesg, RSTRING_LEN(mesg) - 1);
rb_str_cat_cstr(mesg, " is deprecated");
if (removal) {
rb_str_catf(mesg, " and will be removed in Ruby %s", removal);
}
if (suggest) rb_str_catf(mesg, "; use %s instead", suggest);
rb_str_cat_cstr(mesg, "\n");
rb_warn_category(mesg, ID2SYM(id_deprecated));
}
void
rb_warn_deprecated(const char *fmt, const char *suggest, ...)
{
if (!deprecation_warning_enabled()) return;
with_warning_string_from(mesg, 0, fmt, suggest) {
warn_deprecated(mesg, NULL, suggest);
}
}
void
rb_warn_deprecated_to_remove(const char *removal, const char *fmt, const char *suggest, ...)
{
if (!deprecation_warning_enabled()) return;
with_warning_string_from(mesg, 0, fmt, suggest) {
warn_deprecated(mesg, removal, suggest);
}
}
void
rb_warn_reserved_name(const char *coming, const char *fmt, ...)
{
if (!deprecation_warning_enabled()) return;
with_warning_string_from(mesg, 0, fmt, fmt) {
rb_str_set_len(mesg, RSTRING_LEN(mesg) - 1);
rb_str_catf(mesg, " is reserved for Ruby %s\n", coming);
rb_warn_category(mesg, ID2SYM(id_deprecated));
}
}
static inline int
end_with_asciichar(VALUE str, int c)
{
return RB_TYPE_P(str, T_STRING) &&
rb_str_end_with_asciichar(str, c);
}
/* :nodoc: */
static VALUE
warning_write(int argc, VALUE *argv, VALUE buf)
{
while (argc-- > 0) {
rb_str_append(buf, *argv++);
}
return buf;
}
VALUE rb_ec_backtrace_location_ary(const rb_execution_context_t *ec, long lev, long n, bool skip_internal);
static VALUE
rb_warn_m(rb_execution_context_t *ec, VALUE exc, VALUE msgs, VALUE uplevel, VALUE category)
{
VALUE location = Qnil;
int argc = RARRAY_LENINT(msgs);
const VALUE *argv = RARRAY_CONST_PTR(msgs);
if (!NIL_P(ruby_verbose) && argc > 0) {
VALUE str = argv[0];
if (!NIL_P(uplevel)) {
long lev = NUM2LONG(uplevel);
if (lev < 0) {
rb_raise(rb_eArgError, "negative level (%ld)", lev);
}
location = rb_ec_backtrace_location_ary(ec, lev + 1, 1, TRUE);
if (!NIL_P(location)) {
location = rb_ary_entry(location, 0);
}
}
if (argc > 1 || !NIL_P(uplevel) || !end_with_asciichar(str, '\n')) {
VALUE path;
if (NIL_P(uplevel)) {
str = rb_str_tmp_new(0);
}
else if (NIL_P(location) ||
NIL_P(path = rb_funcall(location, rb_intern("path"), 0))) {
str = rb_str_new_cstr("warning: ");
}
else {
str = rb_sprintf("%s:%ld: warning: ",
rb_string_value_ptr(&path),
NUM2LONG(rb_funcall(location, rb_intern("lineno"), 0)));
}
RBASIC_SET_CLASS(str, rb_cWarningBuffer);
rb_io_puts(argc, argv, str);
RBASIC_SET_CLASS(str, rb_cString);
}
if (!NIL_P(category)) {
category = rb_to_symbol_type(category);
rb_warning_category_from_name(category);
}
if (exc == rb_mWarning) {
rb_must_asciicompat(str);
rb_write_error_str(str);
}
else {
rb_warn_category(str, category);
}
}
return Qnil;
}
#define MAX_BUG_REPORTERS 0x100
static struct bug_reporters {
void (*func)(FILE *out, void *data);
void *data;
} bug_reporters[MAX_BUG_REPORTERS];
static int bug_reporters_size;
int
rb_bug_reporter_add(void (*func)(FILE *, void *), void *data)
{
struct bug_reporters *reporter;
if (bug_reporters_size >= MAX_BUG_REPORTERS) {
return 0; /* failed to register */
}
reporter = &bug_reporters[bug_reporters_size++];
reporter->func = func;
reporter->data = data;
return 1;
}
/* returns true if x can not be used as file name */
static bool
path_sep_p(char x)
{
#if defined __CYGWIN__ || defined DOSISH
# define PATH_SEP_ENCODING 1
// Assume that "/" is only the first byte in any encoding.
if (x == ':') return true; // drive letter or ADS
if (x == '\\') return true;
#endif
return x == '/';
}
struct path_string {
const char *ptr;
size_t len;
};
static const char PATHSEP_REPLACE = '!';
static char *
append_pathname(char *p, const char *pe, VALUE str)
{
#ifdef PATH_SEP_ENCODING
rb_encoding *enc = rb_enc_get(str);
#endif
const char *s = RSTRING_PTR(str);
const char *const se = s + RSTRING_LEN(str);
char c;
--pe; // for terminator
while (p < pe && s < se && (c = *s) != '\0') {
if (c == '.') {
if (s == se || !*s) break; // chomp "." basename
if (path_sep_p(s[1])) goto skipsep; // skip "./"
}
else if (path_sep_p(c)) {
// squeeze successive separators
*p++ = PATHSEP_REPLACE;
skipsep:
while (++s < se && path_sep_p(*s));
continue;
}
const char *const ss = s;
while (p < pe && s < se && *s && !path_sep_p(*s)) {
#ifdef PATH_SEP_ENCODING
int n = rb_enc_mbclen(s, se, enc);
#else
const int n = 1;
#endif
p += n;
s += n;
}
if (s > ss) memcpy(p - (s - ss), ss, s - ss);
}
return p;
}
static char *
append_basename(char *p, const char *pe, struct path_string *path, VALUE str)
{
if (!path->ptr) {
#ifdef PATH_SEP_ENCODING
rb_encoding *enc = rb_enc_get(str);
#endif
const char *const b = RSTRING_PTR(str), *const e = RSTRING_END(str), *p = e;
while (p > b) {
if (path_sep_p(p[-1])) {
#ifdef PATH_SEP_ENCODING
const char *t = rb_enc_prev_char(b, p, e, enc);
if (t == p-1) break;
p = t;
#else
break;
#endif
}
else {
--p;
}
}
path->ptr = p;
path->len = e - p;
}
size_t n = path->len;
if (p + n > pe) n = pe - p;
memcpy(p, path->ptr, n);
return p + n;
}
static void
finish_report(FILE *out, rb_pid_t pid)
{
if (out != stdout && out != stderr) fclose(out);
#ifdef HAVE_WORKING_FORK
if (pid > 0) waitpid(pid, NULL, 0);
#endif
}
struct report_expansion {
struct path_string exe, script;
rb_pid_t pid;
time_t time;
};
/*
* Open a bug report file to write. The `RUBY_CRASH_REPORT`
* environment variable can be set to define a template that is used
* to name bug report files. The template can contain % specifiers
* which are substituted by the following values when a bug report
* file is created:
*
* %% A single % character.
* %e The base name of the executable filename.
* %E Pathname of executable, with slashes ('/') replaced by
* exclamation marks ('!').
* %f Similar to %e with the main script filename.
* %F Similar to %E with the main script filename.
* %p PID of dumped process in decimal.
* %t Time of dump, expressed as seconds since the Epoch,
* 1970-01-01 00:00:00 +0000 (UTC).
* %NNN Octal char code, upto 3 digits.
*/
static char *
expand_report_argument(const char **input_template, struct report_expansion *values,
char *buf, size_t size, bool word)
{
char *p = buf;
char *end = buf + size;
const char *template = *input_template;
bool store = true;
if (p >= end-1 || !*template) return NULL;
do {
char c = *template++;
if (word && ISSPACE(c)) break;
if (!store) continue;
if (c == '%') {
size_t n;
switch (c = *template++) {
case 'e':
p = append_basename(p, end, &values->exe, rb_argv0);
continue;
case 'E':
p = append_pathname(p, end, rb_argv0);
continue;
case 'f':
p = append_basename(p, end, &values->script, GET_VM()->orig_progname);
continue;
case 'F':
p = append_pathname(p, end, GET_VM()->orig_progname);
continue;
case 'p':
if (!values->pid) values->pid = getpid();
snprintf(p, end-p, "%" PRI_PIDT_PREFIX "d", values->pid);
p += strlen(p);
continue;
case 't':
if (!values->time) values->time = time(NULL);
snprintf(p, end-p, "%" PRI_TIMET_PREFIX "d", values->time);
p += strlen(p);
continue;
default:
if (c >= '0' && c <= '7') {
c = (unsigned char)ruby_scan_oct(template-1, 3, &n);
template += n - 1;
if (!c) store = false;
}
break;
}
}
if (p < end-1) *p++ = c;
} while (*template);
*input_template = template;
*p = '\0';
return ++p;
}
FILE *ruby_popen_writer(char *const *argv, rb_pid_t *pid);
static FILE *
open_report_path(const char *template, char *buf, size_t size, rb_pid_t *pid)
{
struct report_expansion values = {{0}};
if (!template) return NULL;
if (0) fprintf(stderr, "RUBY_CRASH_REPORT=%s\n", buf);
if (*template == '|') {
char *argv[16], *bufend = buf + size, *p;
int argc;
template++;
for (argc = 0; argc < numberof(argv) - 1; ++argc) {
while (*template && ISSPACE(*template)) template++;
p = expand_report_argument(&template, &values, buf, bufend-buf, true);
if (!p) break;
argv[argc] = buf;
buf = p;
}
argv[argc] = NULL;
if (!p) return ruby_popen_writer(argv, pid);
}
else if (*template) {
expand_report_argument(&template, &values, buf, size, false);
return fopen(buf, "w");
}
return NULL;
}
static const char *crash_report;
/* SIGSEGV handler might have a very small stack. Thus we need to use it carefully. */
#define REPORT_BUG_BUFSIZ 256
static FILE *
bug_report_file(const char *file, int line, rb_pid_t *pid)
{
char buf[REPORT_BUG_BUFSIZ];
const char *report = crash_report;
if (!report) report = getenv("RUBY_CRASH_REPORT");
FILE *out = open_report_path(report, buf, sizeof(buf), pid);
int len = err_position_0(buf, sizeof(buf), file, line);
if (out) {
/* Disable buffering so crash report output is not lost if
* rb_vm_bugreport() triggers a secondary crash (e.g. SIGSEGV
* while walking JIT frames). */
setvbuf(out, NULL, _IONBF, 0);
if ((ssize_t)fwrite(buf, 1, len, out) == (ssize_t)len) return out;
fclose(out);
}
if ((ssize_t)fwrite(buf, 1, len, stderr) == (ssize_t)len) {
return stderr;
}
if ((ssize_t)fwrite(buf, 1, len, stdout) == (ssize_t)len) {
return stdout;
}
return NULL;
}
FUNC_MINIMIZED(static void bug_important_message(FILE *out, const char *const msg, size_t len));
static void
bug_important_message(FILE *out, const char *const msg, size_t len)
{
const char *const endmsg = msg + len;
const char *p = msg;
if (!len) return;
if (isatty(fileno(out))) {
static const char red[] = "\033[;31;1;7m";
static const char green[] = "\033[;32;7m";
static const char reset[] = "\033[m";
const char *e = strchr(p, '\n');
const int w = (int)(e - p);
do {
int i = (int)(e - p);
fputs(*p == ' ' ? green : red, out);
fwrite(p, 1, e - p, out);
for (; i < w; ++i) fputc(' ', out);
fputs(reset, out);
fputc('\n', out);
} while ((p = e + 1) < endmsg && (e = strchr(p, '\n')) != 0 && e > p + 1);
}
fwrite(p, 1, endmsg - p, out);
}
#undef CRASH_REPORTER_MAY_BE_CREATED
#if defined(__APPLE__) && \
(!defined(MAC_OS_X_VERSION_10_6) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6 || defined(__POWERPC__)) /* 10.6 PPC case */
# define CRASH_REPORTER_MAY_BE_CREATED
#endif
static void
preface_dump(FILE *out)
{
#if defined __APPLE__
static const char msg[] = ""
"-- Crash Report log information "
"--------------------------------------------\n"
" See Crash Report log file in one of the following locations:\n"
# ifdef CRASH_REPORTER_MAY_BE_CREATED
" * ~/Library/Logs/CrashReporter\n"
" * /Library/Logs/CrashReporter\n"
# endif
" * ~/Library/Logs/DiagnosticReports\n"
" * /Library/Logs/DiagnosticReports\n"
" for more details.\n"
"Don't forget to include the above Crash Report log file in bug reports.\n"
"\n";
const size_t msglen = sizeof(msg) - 1;
#else
const char *msg = NULL;
const size_t msglen = 0;
#endif
bug_important_message(out, msg, msglen);
}
static void
postscript_dump(FILE *out)
{
#if defined __APPLE__
static const char msg[] = ""
"[IMPORTANT]"
/*" ------------------------------------------------"*/
"\n""Don't forget to include the Crash Report log file under\n"
# ifdef CRASH_REPORTER_MAY_BE_CREATED
"CrashReporter or "
# endif
"DiagnosticReports directory in bug reports.\n"
/*"------------------------------------------------------------\n"*/
"\n";
const size_t msglen = sizeof(msg) - 1;
#else
const char *msg = NULL;
const size_t msglen = 0;
#endif
bug_important_message(out, msg, msglen);
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0)
static void
bug_report_begin_valist(FILE *out, const char *fmt, va_list args)
{
char buf[REPORT_BUG_BUFSIZ];
fputs("[BUG] ", out);
vsnprintf(buf, sizeof(buf), fmt, args);
fputs(buf, out);
snprintf(buf, sizeof(buf), "\n%s\n\n", rb_dynamic_description);
fputs(buf, out);
preface_dump(out);
}
#define bug_report_begin(out, fmt) do { \
va_list args; \
va_start(args, fmt); \
bug_report_begin_valist(out, fmt, args); \
va_end(args); \
} while (0)
static void
bug_report_end(FILE *out, rb_pid_t pid)
{
/* call additional bug reporters */
{
int i;
for (i=0; i<bug_reporters_size; i++) {
struct bug_reporters *reporter = &bug_reporters[i];
(*reporter->func)(out, reporter->data);
}
}
postscript_dump(out);
finish_report(out, pid);
}
#define report_bug(file, line, fmt, ctx) do { \
rb_pid_t pid = -1; \
FILE *out = bug_report_file(file, line, &pid); \
if (out) { \
bug_report_begin(out, fmt); \
rb_vm_bugreport(ctx, out); \
bug_report_end(out, pid); \
} \
} while (0) \
#define report_bug_valist(file, line, fmt, ctx, args) do { \
rb_pid_t pid = -1; \
FILE *out = bug_report_file(file, line, &pid); \
if (out) { \
bug_report_begin_valist(out, fmt, args); \
rb_vm_bugreport(ctx, out); \
bug_report_end(out, pid); \
} \
} while (0) \
void
ruby_set_crash_report(const char *template)
{
crash_report = template;
#if RUBY_DEBUG
rb_pid_t pid = -1;
char buf[REPORT_BUG_BUFSIZ];
FILE *out = open_report_path(template, buf, sizeof(buf), &pid);
if (out) {
time_t t = time(NULL);
fprintf(out, "ruby_test_bug_report: %s", ctime(&t));
finish_report(out, pid);
}
#endif
}
NORETURN(static void die(void));
static void
die(void)
{
#if defined(_WIN32) && defined(RUBY_MSVCRT_VERSION) && RUBY_MSVCRT_VERSION >= 80
/* mingw32 declares in stdlib.h but does not provide. */
_set_abort_behavior( 0, _CALL_REPORTFAULT);
#endif
/* Reset SIGABRT to default so that abort() does not trigger our custom
* handler (sigabrt), which would re-open the crash report file with "w"
* and truncate the report already written by rb_bug(). */
signal(SIGABRT, SIG_DFL);
abort();
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 0)
static void
rb_bug_without_die_internal(const char *fmt, va_list args)
{
const char *file = NULL;
int line = 0;
if (rb_current_execution_context(false)) {
file = rb_source_location_cstr(&line);
}
report_bug_valist(file, line, fmt, NULL, args);
}
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 0)
void
rb_bug_without_die(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
rb_bug_without_die_internal(fmt, args);
va_end(args);
}
void
rb_bug(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
rb_bug_without_die_internal(fmt, args);
va_end(args);
die();
}
void
rb_bug_for_fatal_signal(ruby_sighandler_t default_sighandler, int sig, const void *ctx, const char *fmt, ...)
{
const char *file = NULL;
int line = 0;
if (rb_current_execution_context(false)) {
file = rb_source_location_cstr(&line);
}
report_bug(file, line, fmt, ctx);
if (default_sighandler) default_sighandler(sig);
ruby_default_signal(sig);
die();
}
void
rb_bug_errno(const char *mesg, int errno_arg)
{
if (errno_arg == 0)
rb_bug("%s: errno == 0 (NOERROR)", mesg);
else {
const char *errno_str = rb_strerrno(errno_arg);
if (errno_str)
rb_bug("%s: %s (%s)", mesg, strerror(errno_arg), errno_str);
else
rb_bug("%s: %s (%d)", mesg, strerror(errno_arg), errno_arg);
}
}
/*
* this is safe to call inside signal handler and timer thread
* (which isn't a Ruby Thread object)
*/
#define write_or_abort(fd, str, len) (write((fd), (str), (len)) < 0 ? abort() : (void)0)
#define WRITE_CONST(fd,str) write_or_abort((fd),(str),sizeof(str) - 1)
void
rb_async_bug_errno(const char *mesg, int errno_arg)
{
WRITE_CONST(2, "[ASYNC BUG] ");
write_or_abort(2, mesg, strlen(mesg));
WRITE_CONST(2, "\n");
if (errno_arg == 0) {
WRITE_CONST(2, "errno == 0 (NOERROR)\n");
}
else {
const char *errno_str = rb_strerrno(errno_arg);
if (!errno_str)
errno_str = "undefined errno";
write_or_abort(2, errno_str, strlen(errno_str));
}
WRITE_CONST(2, "\n\n");
write_or_abort(2, rb_dynamic_description, strlen(rb_dynamic_description));
abort();
}
void
rb_report_bug_valist(VALUE file, int line, const char *fmt, va_list args)
{
report_bug_valist(RSTRING_PTR(file), line, fmt, NULL, args);
}
void
rb_assert_failure(const char *file, int line, const char *name, const char *expr)
{
rb_assert_failure_detail(file, line, name, expr, NULL);
}
void
rb_assert_failure_detail(const char *file, int line, const char *name, const char *expr,
const char *fmt, ...)
{
rb_pid_t pid = -1;
FILE *out = bug_report_file(file, line, &pid);
if (out) {
fputs("Assertion Failed: ", out);
if (name) fprintf(out, "%s:", name);
fputs(expr, out);
if (fmt && *fmt) {
va_list args;
va_start(args, fmt);
fputs(": ", out);
vfprintf(out, fmt, args);
va_end(args);
}
fprintf(out, "\n%s\n\n", rb_dynamic_description);
preface_dump(out);
rb_vm_bugreport(NULL, out);
bug_report_end(out, pid);
}
die();
}
static const char builtin_types[][10] = {
"", /* 0x00, */
"Object",
"Class",
"Module",
"Float",
"String",
"Regexp",
"Array",
"Hash",
"Struct",
"Integer",
"File",
"Data", /* internal use: wrapped C pointers */
"MatchData", /* data of $~ */
"Complex",
"Rational",
"", /* 0x10 */
"nil",
"true",
"false",
"Symbol", /* :symbol */
"Integer",
"undef", /* internal use: #undef; should not happen */
"", /* 0x17 */
"", /* 0x18 */
"", /* 0x19 */
"<Memo>", /* internal use: general memo */
"<Node>", /* internal use: syntax tree node */
"<iClass>", /* internal use: mixed-in module holder */
};
const char *
rb_builtin_type_name(int t)
{
const char *name;
if ((unsigned int)t >= numberof(builtin_types)) return 0;
name = builtin_types[t];
if (*name) return name;
return 0;
}
static VALUE
displaying_class_of(VALUE x)
{
switch (x) {
case Qfalse: return rb_fstring_cstr("false");
case Qnil: return rb_fstring_cstr("nil");
case Qtrue: return rb_fstring_cstr("true");
default: return rb_obj_class(x);
}
}
static const char *
builtin_class_name(VALUE x)
{
const char *etype;
if (NIL_P(x)) {
etype = "nil";
}
else if (FIXNUM_P(x)) {
etype = "Integer";
}
else if (SYMBOL_P(x)) {
etype = "Symbol";
}
else if (RB_TYPE_P(x, T_TRUE)) {
etype = "true";
}
else if (RB_TYPE_P(x, T_FALSE)) {
etype = "false";
}
else {
etype = NULL;
}
return etype;
}
const char *
rb_builtin_class_name(VALUE x)
{
const char *etype = builtin_class_name(x);
if (!etype) {
etype = rb_obj_classname(x);
}
return etype;
}
COLDFUNC NORETURN(static void unexpected_type(VALUE, int, int));
#define UNDEF_LEAKED "undef leaked to the Ruby space"
void
rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected)
{
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)",
actual->wrap_struct_name, expected->wrap_struct_name);
}
void
rb_unexpected_object_type(VALUE obj, const char *expected)
{
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)",
displaying_class_of(obj), expected);
}
static void
unexpected_type(VALUE x, int xt, int t)
{
const char *tname = rb_builtin_type_name(t);
VALUE mesg, exc = rb_eFatal;
if (tname) {
rb_unexpected_object_type(x, tname);
}
else if (xt > T_MASK && xt <= 0x3f) {
mesg = rb_sprintf("unknown type 0x%x (0x%x given, probably comes"
" from extension library for ruby 1.8)", t, xt);
}
else {
mesg = rb_sprintf("unknown type 0x%x (0x%x given)", t, xt);
}
rb_exc_raise(rb_exc_new_str(exc, mesg));
}
void
rb_check_type(VALUE x, int t)
{
int xt;
if (RB_UNLIKELY(UNDEF_P(x))) {
rb_bug(UNDEF_LEAKED);
}
if (t == T_DATA) {
/*
* Typed data is not simple `T_DATA`, but in a sense an
* extension of `struct RVALUE`, which are incompatible with
* each other except when inherited.
*
* So it is not enough to just check `T_DATA`, it must be
* identified by its `type` using `Check_TypedStruct` instead.
*/
rb_unexpected_object_type(x, builtin_types[t]);
}
xt = TYPE(x);
if (xt != t) {
unexpected_type(x, xt, t);
}
}
void
rb_unexpected_type(VALUE x, int t)
{
if (RB_UNLIKELY(UNDEF_P(x))) {
rb_bug(UNDEF_LEAKED);
}
unexpected_type(x, TYPE(x), t);
}
#undef rb_typeddata_inherited_p
int
rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent)
{
return rbimpl_typeddata_inherited_p_inline(child, parent);
}
#undef rb_typeddata_is_kind_of
int
rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type)
{
return rbimpl_typeddata_is_kind_of_inline(obj, data_type);
}
#undef rb_typeddata_is_instance_of
int
rb_typeddata_is_instance_of(VALUE obj, const rb_data_type_t *data_type)
{
return rb_typeddata_is_instance_of_inline(obj, data_type);
}
void *
rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type)
{
return rbimpl_check_typeddata(obj, data_type);
}
/* exception classes */
VALUE rb_eException;
VALUE rb_eSystemExit;
VALUE rb_eInterrupt;
VALUE rb_eSignal;
VALUE rb_eFatal;
VALUE rb_eStandardError;
VALUE rb_eRuntimeError;
VALUE rb_eFrozenError;
VALUE rb_eTypeError;
VALUE rb_eArgError;
VALUE rb_eIndexError;
VALUE rb_eKeyError;
VALUE rb_eRangeError;
VALUE rb_eNameError;
VALUE rb_eEncodingError;
VALUE rb_eEncCompatError;
VALUE rb_eNoMethodError;
VALUE rb_eSecurityError;
VALUE rb_eNotImpError;
VALUE rb_eNoMemError;
VALUE rb_cNameErrorMesg;
VALUE rb_eNoMatchingPatternError;
VALUE rb_eNoMatchingPatternKeyError;
VALUE rb_eScriptError;
VALUE rb_eSyntaxError;
VALUE rb_eLoadError;
VALUE rb_eSystemCallError;
VALUE rb_mErrno;
static VALUE rb_eNOERROR;
ID ruby_static_id_cause;
#define id_cause ruby_static_id_cause
static ID id_message, id_detailed_message, id_backtrace;
static ID id_key, id_matchee, id_args, id_Errno, id_errno, id_i_path;
static ID id_receiver, id_recv, id_iseq, id_local_variables;
static ID id_private_call_p, id_top, id_bottom;
#define id_bt idBt
#define id_bt_locations idBt_locations
#define id_mesg idMesg
#define id_name idName
#undef rb_exc_new_cstr
VALUE
rb_exc_new(VALUE etype, const char *ptr, long len)
{
VALUE mesg = rb_str_new(ptr, len);
return rb_class_new_instance(1, &mesg, etype);
}
VALUE
rb_exc_new_cstr(VALUE etype, const char *s)
{
return rb_exc_new(etype, s, strlen(s));
}
VALUE
rb_exc_new_str(VALUE etype, VALUE str)
{
rb_yjit_lazy_push_frame(GET_EC()->cfp->pc);
StringValue(str);
return rb_class_new_instance(1, &str, etype);
}
static VALUE
exc_init(VALUE exc, VALUE mesg)
{
rb_ivar_set(exc, id_mesg, mesg);
rb_ivar_set(exc, id_bt, Qnil);
return exc;
}
/*
* call-seq:
* Exception.new(message = nil) -> exception
*
* Returns a new exception object.
*
* The given +message+ should be
* a {string-convertible object}[rdoc-ref:implicit_conversion.rdoc@String-Convertible+Objects];
* see method #message;
* if not given, the message is the class name of the new instance
* (which may be the name of a subclass):
*
* Examples:
*
* Exception.new # => #<Exception: Exception>
* LoadError.new # => #<LoadError: LoadError> # Subclass of Exception.
* Exception.new('Boom') # => #<Exception: Boom>
*
*/
static VALUE
exc_initialize(int argc, VALUE *argv, VALUE exc)
{
VALUE arg;
arg = (!rb_check_arity(argc, 0, 1) ? Qnil : argv[0]);
return exc_init(exc, arg);
}
/*
* Document-method: exception
*
* call-seq:
* exception(message = nil) -> self or new_exception
*
* Returns an exception object of the same class as +self+;
* useful for creating a similar exception, but with a different message.
*
* With +message+ +nil+, returns +self+:
*
* x0 = StandardError.new('Boom') # => #<StandardError: Boom>
* x1 = x0.exception # => #<StandardError: Boom>
* x0.equal?(x1) # => true
*
* With {string-convertible object}[rdoc-ref:implicit_conversion.rdoc@String-Convertible+Objects]
* +message+ (even the same as the original message),
* returns a new exception object whose class is the same as +self+,
* and whose message is the given +message+:
*
* x1 = x0.exception('Boom') # => #<StandardError: Boom>
* x0.equal?(x1) # => false
*
*/
static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
VALUE exc;
argc = rb_check_arity(argc, 0, 1);
if (argc == 0) return self;
if (argc == 1 && self == argv[0]) return self;
exc = rb_obj_clone(self);
rb_ivar_set(exc, id_mesg, argv[0]);
return exc;
}
/*
* call-seq:
* to_s -> string
*
* Returns a string representation of +self+:
*
* x = RuntimeError.new('Boom')
* x.to_s # => "Boom"
* x = RuntimeError.new
* x.to_s # => "RuntimeError"
*
*/
static VALUE
exc_to_s(VALUE exc)
{
VALUE mesg = rb_attr_get(exc, idMesg);
if (NIL_P(mesg)) return rb_class_name(CLASS_OF(exc));
return rb_String(mesg);
}
/* FIXME: Include eval_error.c */
void rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse);
VALUE
rb_get_message(VALUE exc)
{
VALUE e = rb_check_funcall(exc, id_message, 0, 0);
if (UNDEF_P(e)) return Qnil;
if (!RB_TYPE_P(e, T_STRING)) e = rb_check_string_type(e);
return e;
}
VALUE
rb_get_detailed_message(VALUE exc, VALUE opt)
{
VALUE e;
if (NIL_P(opt)) {
e = rb_check_funcall(exc, id_detailed_message, 0, 0);
}
else {
e = rb_check_funcall_kw(exc, id_detailed_message, 1, &opt, 1);
}
if (UNDEF_P(e)) return Qnil;
if (!RB_TYPE_P(e, T_STRING)) e = rb_check_string_type(e);
return e;
}
/*
* call-seq:
* Exception.to_tty? -> true or false
*
* Returns +true+ if exception messages will be sent to a terminal device.
*/
static VALUE
exc_s_to_tty_p(VALUE self)
{
return RBOOL(rb_stderr_tty_p());
}
static VALUE
check_highlight_keyword(VALUE opt, int auto_tty_detect)
{
VALUE highlight = Qnil;
if (!NIL_P(opt)) {
highlight = rb_hash_lookup(opt, sym_highlight);
switch (highlight) {
default:
rb_bool_expected(highlight, "highlight", TRUE);
UNREACHABLE;
case Qtrue: case Qfalse: case Qnil: break;
}
}
if (NIL_P(highlight)) {
highlight = RBOOL(auto_tty_detect && rb_stderr_tty_p());
}
return highlight;
}
static VALUE
check_order_keyword(VALUE opt)
{
VALUE order = Qnil;
if (!NIL_P(opt)) {
static VALUE kw_order;
if (!kw_order) kw_order = ID2SYM(rb_intern_const("order"));
order = rb_hash_lookup(opt, kw_order);
if (order != Qnil) {
ID id = rb_check_id(&order);
if (id == id_bottom) order = Qtrue;
else if (id == id_top) order = Qfalse;
else {
rb_raise(rb_eArgError, "expected :top or :bottom as "
"order: %+"PRIsVALUE, order);
}
}
}
if (NIL_P(order)) order = Qfalse;
return order;
}
/*
* call-seq:
* full_message(highlight: true, order: :top) -> string
*
* Returns an enhanced message string:
*
* - Includes the exception class name.
* - If the value of keyword +highlight+ is true (not +nil+ or +false+),
* includes bolding ANSI codes (see below) to enhance the appearance of the message.
* - Includes the {backtrace}[rdoc-ref:exceptions.md@Backtraces]:
*
* - If the value of keyword +order+ is +:top+ (the default),
* lists the error message and the innermost backtrace entry first.
* - If the value of keyword +order+ is +:bottom+,
* lists the error message the innermost entry last.
*
* Example:
*
* def baz
* begin
* 1 / 0
* rescue => x
* pp x.message
* pp x.full_message(highlight: false).split("\n")
* pp x.full_message.split("\n")
* end
* end
* def bar; baz; end
* def foo; bar; end
* foo
*
* Output:
*
* "divided by 0"
* ["t.rb:3:in 'Integer#/': divided by 0 (ZeroDivisionError)",
* "\tfrom t.rb:3:in 'Object#baz'",
* "\tfrom t.rb:10:in 'Object#bar'",
* "\tfrom t.rb:11:in 'Object#foo'",
* "\tfrom t.rb:12:in '<main>'"]
* ["t.rb:3:in 'Integer#/': \e[1mdivided by 0 (\e[1;4mZeroDivisionError\e[m\e[1m)\e[m",
* "\tfrom t.rb:3:in 'Object#baz'",
* "\tfrom t.rb:10:in 'Object#bar'",
* "\tfrom t.rb:11:in 'Object#foo'",
* "\tfrom t.rb:12:in '<main>'"]
*
* An overriding method should be careful with ANSI code enhancements;
* see {Messages}[rdoc-ref:exceptions.md@Messages].
*/
static VALUE
exc_full_message(int argc, VALUE *argv, VALUE exc)
{
VALUE opt, str, emesg, errat;
VALUE highlight, order;
rb_scan_args(argc, argv, "0:", &opt);
highlight = check_highlight_keyword(opt, 1);
order = check_order_keyword(opt);
{
if (NIL_P(opt)) opt = rb_hash_new();
rb_hash_aset(opt, sym_highlight, highlight);
}
str = rb_str_new2("");
errat = rb_get_backtrace(exc);
emesg = rb_get_detailed_message(exc, opt);
rb_error_write(exc, emesg, errat, str, opt, highlight, order);
return str;
}
/*
* call-seq:
* message -> string
*
* Returns #to_s.
*
* See {Messages}[rdoc-ref:exceptions.md@Messages].
*/
static VALUE
exc_message(VALUE exc)
{
return rb_funcallv(exc, idTo_s, 0, 0);
}
// Whether error_highlight, did_you_mean, and syntax_suggest have already
// been loaded (lazily on the first error, or eagerly via Process.warmup).
// Ractors run in parallel, so the load is claimed by an atomic exchange.
static rb_atomic_t decoration_gems_loaded = 0;
static VALUE
load_decoration_gem(VALUE feature)
{
// The C-level require bypasses Kernel#require monkeypatches;
// displaying an error must not invoke or depend on them.
return rb_require_string(feature);
}
// Require error_highlight, did_you_mean, and syntax_suggest.
// rb_define_gem_modules registers an autoload entry for each enabled gem;
// disabled gems have no entry and are skipped.
static void
require_decoration_gems(void)
{
// Loading must not disturb the caller's $!: it is called while
// displaying an exception. rb_protect does not preserve errinfo, so
// save and restore it around the requires, leaving $! untouched
// whether or not a require raises.
VALUE saved_errinfo = rb_errinfo();
static const char *const gems[] = {"ErrorHighlight", "DidYouMean", "SyntaxSuggest"};
for (size_t i = 0; i < numberof(gems); i++) {
VALUE feature = rb_autoload_p(rb_cObject, rb_intern(gems[i]));
if (NIL_P(feature)) continue;
int state;
rb_protect(load_decoration_gem, feature, &state);
(void)state;
}
rb_set_errinfo(saved_errinfo);
}
// Load the decoration gems on the first error display instead of at boot.
// In non-main Ractors rb_require_string delegates the require to the main
// Ractor, so this works from any Ractor.
// Returns whether the caller should re-dispatch to pick up the
// detailed_message decorators the gems prepend.
static bool
lazy_load_decoration_gems(VALUE exc)
{
if (ATOMIC_EXCHANGE(decoration_gems_loaded, 1)) return false;
// When entered through super from a decorator already sitting above
// this method, the caller decorates the result; re-dispatching would
// decorate it twice.
bool redispatch = rb_method_basic_definition_p(CLASS_OF(exc), id_detailed_message);
require_decoration_gems();
return redispatch;
}
// Load the decoration gems eagerly, e.g. from Process.warmup before a
// pre-forking server forks, so the prepended detailed_message decorators
// land in shared memory and do not bust method caches at runtime.
void
rb_eager_load_detailed_message_extension(void)
{
if (ATOMIC_EXCHANGE(decoration_gems_loaded, 1)) return;
require_decoration_gems();
}
/*
* call-seq:
* detailed_message(highlight: false, **kwargs) -> string
*
* Returns the message string with enhancements:
*
* - Includes the exception class name in the first line.
* - If the value of keyword +highlight+ is +true+,
* includes bolding and underlining ANSI codes (see below)
* to enhance the appearance of the message.
*
* Examples:
*
* begin
* 1 / 0
* rescue => x
* p x.message
* p x.detailed_message # Class name added.
* p x.detailed_message(highlight: true) # Class name, bolding, and underlining added.
* end
*
* Output:
*
* "divided by 0"
* "divided by 0 (ZeroDivisionError)"
* "\e[1mdivided by 0 (\e[1;4mZeroDivisionError\e[m\e[1m)\e[m"
*
* This method is overridden by some gems in the Ruby standard library to add information:
*
* - DidYouMean::Correctable#detailed_message.
* - ErrorHighlight::CoreExt#detailed_message.
* - SyntaxSuggest#detailed_message.
*
* An overriding method must be tolerant of passed keyword arguments,
* which may include (but may not be limited to):
*
* - +:highlight+.
* - +:did_you_mean+.
* - +:error_highlight+.
* - +:syntax_suggest+.
*
* An overriding method should also be careful with ANSI code enhancements;
* see {Messages}[rdoc-ref:exceptions.md@Messages].
*/
static VALUE
exc_detailed_message(int argc, VALUE *argv, VALUE exc)
{
if (lazy_load_decoration_gems(exc)) {
return rb_funcallv_kw(exc, id_detailed_message, argc, argv, RB_PASS_CALLED_KEYWORDS);
}
VALUE opt;
rb_scan_args(argc, argv, "0:", &opt);
VALUE highlight = check_highlight_keyword(opt, 0);
extern VALUE rb_decorate_message(const VALUE eclass, VALUE emesg, int highlight);
return rb_decorate_message(CLASS_OF(exc), rb_get_message(exc), RTEST(highlight));
}
/*
* call-seq:
* inspect -> string
*
* Returns a string representation of +self+:
*
* x = RuntimeError.new('Boom')
* x.inspect # => "#<RuntimeError: Boom>"
* x = RuntimeError.new
* x.inspect # => "#<RuntimeError: RuntimeError>"
*
*/
static VALUE
exc_inspect(VALUE exc)
{
VALUE str, klass;
klass = CLASS_OF(exc);
exc = rb_obj_as_string(exc);
if (RSTRING_LEN(exc) == 0) {
return rb_class_name(klass);
}
str = rb_str_buf_new2("#<");
klass = rb_class_name(klass);
rb_str_buf_append(str, klass);
if (RTEST(rb_str_include(exc, rb_str_new2("\n")))) {
rb_str_catf(str, ":%+"PRIsVALUE, exc);
}
else {
rb_str_buf_cat(str, ": ", 2);
rb_str_buf_append(str, exc);
}
rb_str_buf_cat(str, ">", 1);
return str;
}
/*
* call-seq:
* backtrace -> array or nil
*
* Returns the backtrace (the list of code locations that led to the exception),
* as an array of strings.
*
* Example (assuming the code is stored in the file named <tt>t.rb</tt>):
*
* def division(numerator, denominator)
* numerator / denominator
* end
*
* begin
* division(1, 0)
* rescue => ex
* p ex.backtrace
* # ["t.rb:2:in 'Integer#/'", "t.rb:2:in 'Object#division'", "t.rb:6:in '<main>'"]
* loc = ex.backtrace.first
* p loc.class
* # String
* end
*
* The value returned by this method might be adjusted when raising (see Kernel#raise),
* or during intermediate handling by #set_backtrace.
*
* See also #backtrace_locations that provide the same value, as structured objects.
* (Note though that two values might not be consistent with each other when
* backtraces are manually adjusted.)
*
* see {Backtraces}[rdoc-ref:exceptions.md@Backtraces].
*/
static VALUE
exc_backtrace(VALUE exc)
{
VALUE obj;
obj = rb_attr_get(exc, id_bt);
if (rb_backtrace_p(obj)) {
obj = rb_backtrace_to_str_ary(obj);
/* rb_ivar_set(exc, id_bt, obj); */
}
return obj;
}
static VALUE rb_check_backtrace(VALUE);
VALUE
rb_get_backtrace(VALUE exc)
{
ID mid = id_backtrace;
VALUE info;
if (rb_method_basic_definition_p(CLASS_OF(exc), id_backtrace)) {
VALUE klass = rb_eException;
rb_execution_context_t *ec = GET_EC();
if (NIL_P(exc))
return Qnil;
EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_CALL, exc, mid, mid, klass, Qundef);
info = exc_backtrace(exc);
EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_RETURN, exc, mid, mid, klass, info);
}
else {
info = rb_funcallv(exc, mid, 0, 0);
}
if (NIL_P(info)) return Qnil;
return rb_check_backtrace(info);
}
/*
* call-seq:
* backtrace_locations -> array or nil
*
* Returns the backtrace (the list of code locations that led to the exception),
* as an array of Thread::Backtrace::Location instances.
*
* Example (assuming the code is stored in the file named <tt>t.rb</tt>):
*
* def division(numerator, denominator)
* numerator / denominator
* end
*
* begin
* division(1, 0)
* rescue => ex
* p ex.backtrace_locations
* # ["t.rb:2:in 'Integer#/'", "t.rb:2:in 'Object#division'", "t.rb:6:in '<main>'"]
* loc = ex.backtrace_locations.first
* p loc.class
* # Thread::Backtrace::Location
* p loc.path
* # "t.rb"
* p loc.lineno
* # 2
* p loc.label
* # "Integer#/"
* end
*
* The value returned by this method might be adjusted when raising (see Kernel#raise),
* or during intermediate handling by #set_backtrace.
*
* See also #backtrace that provide the same value as an array of strings.
* (Note though that two values might not be consistent with each other when
* backtraces are manually adjusted.)
*
* See {Backtraces}[rdoc-ref:exceptions.md@Backtraces].
*/
static VALUE
exc_backtrace_locations(VALUE exc)
{
VALUE obj;
obj = rb_attr_get(exc, id_bt_locations);
if (!NIL_P(obj)) {
obj = rb_backtrace_to_location_ary(obj);
}
return obj;
}
static VALUE
rb_check_backtrace(VALUE bt)
{
long i;
static const char err[] = "backtrace must be an Array of String or an Array of Thread::Backtrace::Location";
if (!NIL_P(bt)) {
if (RB_TYPE_P(bt, T_STRING)) return rb_ary_new3(1, bt);
if (rb_backtrace_p(bt)) return bt;
if (!RB_TYPE_P(bt, T_ARRAY)) {
rb_raise(rb_eTypeError, err);
}
for (i=0;i<RARRAY_LEN(bt);i++) {
VALUE e = RARRAY_AREF(bt, i);
if (!RB_TYPE_P(e, T_STRING)) {
rb_raise(rb_eTypeError, err);
}
}
}
return bt;
}
/*
* call-seq:
* set_backtrace(value) -> value
*
* Sets the backtrace value for +self+; returns the given +value+.
*
* The +value+ might be:
*
* - an array of Thread::Backtrace::Location;
* - an array of String instances;
* - a single String instance; or
* - +nil+.
*
* Using array of Thread::Backtrace::Location is the most consistent
* option: it sets both #backtrace and #backtrace_locations. It should be
* preferred when possible. The suitable array of locations can be obtained
* from Kernel#caller_locations, copied from another error, or just set to
* the adjusted result of the current error's #backtrace_locations:
*
* require 'json'
*
* def parse_payload(text)
* JSON.parse(text) # test.rb, line 4
* rescue JSON::ParserError => ex
* ex.set_backtrace(ex.backtrace_locations[2...])
* raise
* end
*
* parse_payload('{"wrong: "json"')
* # test.rb:4:in 'Object#parse_payload': unexpected token at '{"wrong: "json"' (JSON::ParserError)
* #
* # An error points to the body of parse_payload method,
* # hiding the parts of the backtrace related to the internals
* # of the "json" library
*
* # The error has both #backtace and #backtrace_locations set
* # consistently:
* begin
* parse_payload('{"wrong: "json"')
* rescue => ex
* p ex.backtrace
* # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"]
* p ex.backtrace_locations
* # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"]
* end
*
* When the desired stack of locations is not available and should
* be constructed from scratch, an array of strings or a singular
* string can be used. In this case, only #backtrace is affected:
*
* def parse_payload(text)
* JSON.parse(text)
* rescue JSON::ParserError => ex
* ex.set_backtrace(["dsl.rb:34", "framework.rb:1"])
* # The error have the new value in #backtrace:
* p ex.backtrace
* # ["dsl.rb:34", "framework.rb:1"]
*
* # but the original one in #backtrace_locations
* p ex.backtrace_locations
* # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...]
* end
*
* parse_payload('{"wrong: "json"')
*
* Calling #set_backtrace with +nil+ clears up #backtrace but doesn't affect
* #backtrace_locations:
*
* def parse_payload(text)
* JSON.parse(text)
* rescue JSON::ParserError => ex
* ex.set_backtrace(nil)
* p ex.backtrace
* # nil
* p ex.backtrace_locations
* # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...]
* end
*
* parse_payload('{"wrong: "json"')
*
* On reraising of such an exception, both #backtrace and #backtrace_locations
* is set to the place of reraising:
*
* def parse_payload(text)
* JSON.parse(text)
* rescue JSON::ParserError => ex
* ex.set_backtrace(nil)
* raise # test.rb, line 7
* end
*
* begin
* parse_payload('{"wrong: "json"')
* rescue => ex
* p ex.backtrace
* # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"]
* p ex.backtrace_locations
* # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"]
* end
*
* See {Backtraces}[rdoc-ref:exceptions.md@Backtraces].
*/
static VALUE
exc_set_backtrace(VALUE exc, VALUE bt)
{
VALUE btobj = rb_location_ary_to_backtrace(bt);
if (RTEST(btobj)) {
rb_ivar_set(exc, id_bt, btobj);
rb_ivar_set(exc, id_bt_locations, btobj);
return bt;
}
else {
return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt));
}
}
VALUE
rb_exc_set_backtrace(VALUE exc, VALUE bt)
{
return exc_set_backtrace(exc, bt);
}
/*
* call-seq:
* cause -> exception or nil
*
* Returns the previous value of global variable <tt>$!</tt>,
* which may be +nil+
* (see {Global Variables}[rdoc-ref:exceptions.md@Global+Variables]):
*
* begin
* raise('Boom 0')
* rescue => x0
* puts "Exception: #{x0}; $!: #{$!}; cause: #{x0.cause.inspect}."
* begin
* raise('Boom 1')
* rescue => x1
* puts "Exception: #{x1}; $!: #{$!}; cause: #{x1.cause}."
* begin
* raise('Boom 2')
* rescue => x2
* puts "Exception: #{x2}; $!: #{$!}; cause: #{x2.cause}."
* end
* end
* end
*
* Output:
*
* Exception: Boom 0; $!: Boom 0; cause: nil.
* Exception: Boom 1; $!: Boom 1; cause: Boom 0.
* Exception: Boom 2; $!: Boom 2; cause: Boom 1.
*
*/
static VALUE
exc_cause(VALUE exc)
{
return rb_attr_get(exc, id_cause);
}
static VALUE
try_convert_to_exception(VALUE obj)
{
return rb_check_funcall(obj, idException, 0, 0);
}
/*
* call-seq:
* self == other -> true or false
*
* Returns whether +other+ is the same class as +self+
* and its #message and #backtrace are equal to those of +self+.
*
*/
static VALUE
exc_equal(VALUE exc, VALUE obj)
{
VALUE mesg, backtrace;
if (exc == obj) return Qtrue;
if (rb_obj_class(exc) != rb_obj_class(obj)) {
int state;
obj = rb_protect(try_convert_to_exception, obj, &state);
if (state || UNDEF_P(obj)) {
rb_set_errinfo(Qnil);
return Qfalse;
}
if (rb_obj_class(exc) != rb_obj_class(obj)) return Qfalse;
mesg = rb_check_funcall(obj, id_message, 0, 0);
if (UNDEF_P(mesg)) return Qfalse;
backtrace = rb_check_funcall(obj, id_backtrace, 0, 0);
if (UNDEF_P(backtrace)) return Qfalse;
}
else {
mesg = rb_attr_get(obj, id_mesg);
backtrace = exc_backtrace(obj);
}
if (!rb_equal(rb_attr_get(exc, id_mesg), mesg))
return Qfalse;
return rb_equal(exc_backtrace(exc), backtrace);
}
/*
* call-seq:
* SystemExit.new -> system_exit
* SystemExit.new(status) -> system_exit
* SystemExit.new(status, msg) -> system_exit
* SystemExit.new(msg) -> system_exit
*
* Create a new +SystemExit+ exception with the given status and message.
* Status is true, false, or an integer.
* If status is not given, true is used.
*/
static VALUE
exit_initialize(int argc, VALUE *argv, VALUE exc)
{
VALUE status;
if (argc > 0) {
status = *argv;
switch (status) {
case Qtrue:
status = INT2FIX(EXIT_SUCCESS);
++argv;
--argc;
break;
case Qfalse:
status = INT2FIX(EXIT_FAILURE);
++argv;
--argc;
break;
default:
status = rb_check_to_int(status);
if (NIL_P(status)) {
status = INT2FIX(EXIT_SUCCESS);
}
else {
#if EXIT_SUCCESS != 0
if (status == INT2FIX(0))
status = INT2FIX(EXIT_SUCCESS);
#endif
++argv;
--argc;
}
break;
}
}
else {
status = INT2FIX(EXIT_SUCCESS);
}
rb_call_super(argc, argv);
rb_ivar_set(exc, id_status, status);
return exc;
}
/*
* call-seq:
* system_exit.status -> integer
*
* Return the status value associated with this system exit.
*/
static VALUE
exit_status(VALUE exc)
{
return rb_attr_get(exc, id_status);
}
/*
* call-seq:
* system_exit.success? -> true or false
*
* Returns +true+ if exiting successful, +false+ if not.
*/
static VALUE
exit_success_p(VALUE exc)
{
VALUE status_val = rb_attr_get(exc, id_status);
int status;
if (NIL_P(status_val))
return Qtrue;
status = NUM2INT(status_val);
return RBOOL(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS);
}
static VALUE
err_init_recv(VALUE exc, VALUE recv)
{
if (!UNDEF_P(recv)) rb_ivar_set(exc, id_recv, recv);
return exc;
}
/*
* call-seq:
* FrozenError.new(msg=nil, receiver: nil) -> frozen_error
*
* Construct a new FrozenError exception. If given the <i>receiver</i>
* parameter may subsequently be examined using the FrozenError#receiver
* method.
*
* a = [].freeze
* raise FrozenError.new("can't modify frozen array", receiver: a)
*/
static VALUE
frozen_err_initialize(int argc, VALUE *argv, VALUE self)
{
ID keywords[1];
VALUE values[numberof(keywords)], options;
argc = rb_scan_args(argc, argv, "*:", NULL, &options);
keywords[0] = id_receiver;
rb_get_kwargs(options, keywords, 0, numberof(values), values);
rb_call_super(argc, argv);
err_init_recv(self, values[0]);
return self;
}
/*
* Document-method: FrozenError#receiver
* call-seq:
* frozen_error.receiver -> object
*
* Return the receiver associated with this FrozenError exception.
*/
#define frozen_err_receiver name_err_receiver
void
rb_name_error(ID id, const char *fmt, ...)
{
VALUE exc, argv[2];
va_list args;
va_start(args, fmt);
argv[0] = rb_vsprintf(fmt, args);
va_end(args);
argv[1] = ID2SYM(id);
exc = rb_class_new_instance(2, argv, rb_eNameError);
rb_exc_raise(exc);
}
void
rb_name_error_str(VALUE str, const char *fmt, ...)
{
VALUE exc, argv[2];
va_list args;
va_start(args, fmt);
argv[0] = rb_vsprintf(fmt, args);
va_end(args);
argv[1] = str;
exc = rb_class_new_instance(2, argv, rb_eNameError);
rb_exc_raise(exc);
}
static VALUE
name_err_init_attr(VALUE exc, VALUE recv, VALUE method)
{
const rb_execution_context_t *ec = GET_EC();
rb_control_frame_t *cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(ec->cfp);
cfp = rb_vm_get_ruby_level_next_cfp(ec, cfp);
rb_ivar_set(exc, id_name, method);
err_init_recv(exc, recv);
if (cfp && VM_FRAME_TYPE(cfp) != VM_FRAME_MAGIC_DUMMY) {
rb_ivar_set(exc, id_iseq, rb_iseqw_new(CFP_ISEQ(cfp)));
}
return exc;
}
/*
* call-seq:
* NameError.new(msg=nil, name=nil, receiver: nil) -> name_error
*
* Construct a new NameError exception. If given the <i>name</i>
* parameter may subsequently be examined using the NameError#name
* method. <i>receiver</i> parameter allows to pass object in
* context of which the error happened. Example:
*
* [1, 2, 3].method(:rject) # NameError with name "rject" and receiver: Array
* [1, 2, 3].singleton_method(:rject) # NameError with name "rject" and receiver: [1, 2, 3]
*/
static VALUE
name_err_initialize(int argc, VALUE *argv, VALUE self)
{
ID keywords[1];
VALUE values[numberof(keywords)], name, options;
argc = rb_scan_args(argc, argv, "*:", NULL, &options);
keywords[0] = id_receiver;
rb_get_kwargs(options, keywords, 0, numberof(values), values);
name = (argc > 1) ? argv[--argc] : Qnil;
rb_call_super(argc, argv);
name_err_init_attr(self, values[0], name);
return self;
}
static VALUE rb_name_err_mesg_new(VALUE mesg, VALUE recv, VALUE method);
static VALUE
name_err_init(VALUE exc, VALUE mesg, VALUE recv, VALUE method)
{
exc_init(exc, rb_name_err_mesg_new(mesg, recv, method));
return name_err_init_attr(exc, recv, method);
}
VALUE
rb_name_err_new(VALUE mesg, VALUE recv, VALUE method)
{
VALUE exc = rb_obj_alloc(rb_eNameError);
return name_err_init(exc, mesg, recv, method);
}
/*
* call-seq:
* name_error.name -> string or nil
*
* Return the name associated with this NameError exception.
*/
static VALUE
name_err_name(VALUE self)
{
return rb_attr_get(self, id_name);
}
/*
* call-seq:
* name_error.local_variables -> array
*
* Return a list of the local variable names defined where this
* NameError exception was raised.
*
* Internal use only.
*/
static VALUE
name_err_local_variables(VALUE self)
{
VALUE vars = rb_attr_get(self, id_local_variables);
if (NIL_P(vars)) {
VALUE iseqw = rb_attr_get(self, id_iseq);
if (!NIL_P(iseqw)) vars = rb_iseqw_local_variables(iseqw);
if (NIL_P(vars)) vars = rb_ary_new();
rb_ivar_set(self, id_local_variables, vars);
}
return vars;
}
static VALUE
nometh_err_init_attr(VALUE exc, VALUE args, int priv)
{
rb_ivar_set(exc, id_args, args);
rb_ivar_set(exc, id_private_call_p, RBOOL(priv));
return exc;
}
/*
* call-seq:
* NoMethodError.new(msg=nil, name=nil, args=nil, private=false, receiver: nil) -> no_method_error
*
* Construct a NoMethodError exception for a method of the given name
* called with the given arguments. The name may be accessed using
* the <code>#name</code> method on the resulting object, and the
* arguments using the <code>#args</code> method.
*
* If <i>private</i> argument were passed, it designates method was
* attempted to call in private context, and can be accessed with
* <code>#private_call?</code> method.
*
* <i>receiver</i> argument stores an object whose method was called.
*/
static VALUE
nometh_err_initialize(int argc, VALUE *argv, VALUE self)
{
int priv;
VALUE args, options;
argc = rb_scan_args(argc, argv, "*:", NULL, &options);
priv = (argc > 3) && (--argc, RTEST(argv[argc]));
args = (argc > 2) ? argv[--argc] : Qnil;
if (!NIL_P(options)) argv[argc++] = options;
rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS);
return nometh_err_init_attr(self, args, priv);
}
VALUE
rb_nomethod_err_new(VALUE mesg, VALUE recv, VALUE method, VALUE args, int priv)
{
VALUE exc = rb_obj_alloc(rb_eNoMethodError);
name_err_init(exc, mesg, recv, method);
return nometh_err_init_attr(exc, args, priv);
}
typedef struct name_error_message_struct {
VALUE mesg;
VALUE recv;
VALUE name;
} name_error_message_t;
static void
name_err_mesg_mark_and_move(void *p)
{
name_error_message_t *ptr = (name_error_message_t *)p;
rb_gc_mark_and_move(&ptr->mesg);
rb_gc_mark_and_move(&ptr->recv);
rb_gc_mark_and_move(&ptr->name);
}
static const rb_data_type_t name_err_mesg_data_type = {
"name_err_mesg",
{
name_err_mesg_mark_and_move,
RUBY_TYPED_DEFAULT_FREE,
NULL, // No external memory to report,
name_err_mesg_mark_and_move,
},
0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
};
/* :nodoc: */
static VALUE
rb_name_err_mesg_init(VALUE klass, VALUE mesg, VALUE recv, VALUE name)
{
name_error_message_t *message;
VALUE result = TypedData_Make_Struct(klass, name_error_message_t, &name_err_mesg_data_type, message);
RB_OBJ_WRITE(result, &message->mesg, mesg);
RB_OBJ_WRITE(result, &message->recv, recv);
RB_OBJ_WRITE(result, &message->name, name);
return result;
}
/* :nodoc: */
static VALUE
rb_name_err_mesg_new(VALUE mesg, VALUE recv, VALUE method)
{
return rb_name_err_mesg_init(rb_cNameErrorMesg, mesg, recv, method);
}
/* :nodoc: */
static VALUE
name_err_mesg_alloc(VALUE klass)
{
return rb_name_err_mesg_init(klass, Qnil, Qnil, Qnil);
}
/* :nodoc: */
static VALUE
name_err_mesg_init_copy(VALUE obj1, VALUE obj2)
{
if (obj1 == obj2) return obj1;
rb_obj_init_copy(obj1, obj2);
name_error_message_t *ptr1, *ptr2;
TypedData_Get_Struct(obj1, name_error_message_t, &name_err_mesg_data_type, ptr1);
TypedData_Get_Struct(obj2, name_error_message_t, &name_err_mesg_data_type, ptr2);
RB_OBJ_WRITE(obj1, &ptr1->mesg, ptr2->mesg);
RB_OBJ_WRITE(obj1, &ptr1->recv, ptr2->recv);
RB_OBJ_WRITE(obj1, &ptr1->name, ptr2->name);
return obj1;
}
/* :nodoc: */
static VALUE
name_err_mesg_equal(VALUE obj1, VALUE obj2)
{
if (obj1 == obj2) return Qtrue;
if (rb_obj_class(obj2) != rb_cNameErrorMesg)
return Qfalse;
name_error_message_t *ptr1, *ptr2;
TypedData_Get_Struct(obj1, name_error_message_t, &name_err_mesg_data_type, ptr1);
TypedData_Get_Struct(obj2, name_error_message_t, &name_err_mesg_data_type, ptr2);
if (!rb_equal(ptr1->mesg, ptr2->mesg)) return Qfalse;
if (!rb_equal(ptr1->recv, ptr2->recv)) return Qfalse;
if (!rb_equal(ptr1->name, ptr2->name)) return Qfalse;
return Qtrue;
}
/* :nodoc: */
static VALUE
name_err_mesg_receiver_name(VALUE obj)
{
if (RB_SPECIAL_CONST_P(obj)) return Qundef;
if (RB_BUILTIN_TYPE(obj) == T_MODULE || RB_BUILTIN_TYPE(obj) == T_CLASS) {
return rb_check_funcall(obj, rb_intern("name"), 0, 0);
}
return Qundef;
}
/* :nodoc: */
static VALUE
name_err_mesg_to_str(VALUE obj)
{
name_error_message_t *ptr;
TypedData_Get_Struct(obj, name_error_message_t, &name_err_mesg_data_type, ptr);
VALUE mesg = ptr->mesg;
if (NIL_P(mesg)) return Qnil;
else {
struct RString s_str = {RBASIC_INIT}, c_str = {RBASIC_INIT}, d_str = {RBASIC_INIT};
VALUE c, s, d = 0, args[4], c2;
int state = 0;
rb_encoding *usascii = rb_usascii_encoding();
#define rb_memsearch_lit(str, v) \
rb_memsearch((str), rb_strlen_lit(str), RSTRING_PTR(v), RSTRING_LEN(v), rb_enc_get(v))
#define FAKE_CSTR(v, str) rb_setup_fake_str((v), (str), rb_strlen_lit(str), usascii)
c = s = FAKE_CSTR(&s_str, "");
obj = ptr->recv;
switch (obj) {
case Qnil:
c = d = FAKE_CSTR(&d_str, "nil");
break;
case Qtrue:
c = d = FAKE_CSTR(&d_str, "true");
break;
case Qfalse:
c = d = FAKE_CSTR(&d_str, "false");
break;
default:
if (rb_memsearch_lit("%2$s", mesg) >= 0) {
d = rb_protect(name_err_mesg_receiver_name, obj, &state);
if (state || NIL_OR_UNDEF_P(d))
d = rb_protect(rb_inspect, obj, &state);
if (state) {
rb_set_errinfo(Qnil);
}
d = rb_check_string_type(d);
if (NIL_P(d)) {
d = rb_any_to_s(obj);
}
}
if (!RB_SPECIAL_CONST_P(obj)) {
switch (RB_BUILTIN_TYPE(obj)) {
case T_MODULE:
s = FAKE_CSTR(&s_str, "module ");
c = obj;
break;
case T_CLASS:
s = FAKE_CSTR(&s_str, "class ");
c = obj;
break;
default:
goto object;
}
}
else {
VALUE klass;
object:
klass = CLASS_OF(obj);
if (RB_TYPE_P(klass, T_CLASS) && RCLASS_SINGLETON_P(klass)) {
s = FAKE_CSTR(&s_str, "");
if (obj == rb_vm_top_self()) {
c = FAKE_CSTR(&c_str, "main");
}
else {
c = rb_any_to_s(obj);
}
break;
}
else {
s = FAKE_CSTR(&s_str, "an instance of ");
c = rb_class_real(klass);
}
}
c2 = rb_protect(name_err_mesg_receiver_name, c, &state);
if (state || NIL_OR_UNDEF_P(c2))
c2 = rb_protect(rb_inspect, c, &state);
if (state) {
rb_set_errinfo(Qnil);
}
c2 = rb_check_string_type(c2);
if (NIL_P(c2)) {
c2 = rb_any_to_s(c);
}
c = c2;
break;
}
args[0] = rb_obj_as_string(ptr->name);
args[1] = d;
args[2] = s;
args[3] = c;
mesg = rb_str_format(4, args, mesg);
}
return mesg;
}
/* :nodoc: */
static VALUE
name_err_mesg_dump(VALUE obj, VALUE limit)
{
return name_err_mesg_to_str(obj);
}
/* :nodoc: */
static VALUE
name_err_mesg_load(VALUE klass, VALUE str)
{
return str;
}
/*
* call-seq:
* name_error.receiver -> object
*
* Return the receiver associated with this NameError exception.
*/
static VALUE
name_err_receiver(VALUE self)
{
VALUE recv = rb_ivar_lookup(self, id_recv, Qundef);
if (!UNDEF_P(recv)) return recv;
VALUE mesg = rb_attr_get(self, id_mesg);
if (!rb_typeddata_is_kind_of(mesg, &name_err_mesg_data_type)) {
rb_raise(rb_eArgError, "no receiver is available");
}
name_error_message_t *ptr;
TypedData_Get_Struct(mesg, name_error_message_t, &name_err_mesg_data_type, ptr);
return ptr->recv;
}
/*
* call-seq:
* no_method_error.args -> obj
*
* Return the arguments passed in as the third parameter to
* the constructor.
*/
static VALUE
nometh_err_args(VALUE self)
{
return rb_attr_get(self, id_args);
}
/*
* call-seq:
* no_method_error.private_call? -> true or false
*
* Return true if the caused method was called as private.
*/
static VALUE
nometh_err_private_call_p(VALUE self)
{
return rb_attr_get(self, id_private_call_p);
}
static const char *
type_err_cname(VALUE val)
{
if (NIL_P(val)) {
return "nil";
}
else if (val == Qtrue) {
return "true";
}
else if (val == Qfalse) {
return "false";
}
return NULL;
}
NORETURN(static void type_err_raise(VALUE val, const char *tname, const char *msg));
static void
type_err_raise(VALUE val, const char *tname, const char *msg)
{
const char *cname = type_err_cname(val);
rb_encoding *enc = rb_utf8_encoding();
if (cname) {
rb_enc_raise(enc, rb_eTypeError, "%s %s into %s", msg, cname, tname);
}
rb_enc_raise(enc, rb_eTypeError, "%s %"PRIsVALUE" into %s", msg, rb_obj_class(val), tname);
}
NORETURN(void rb_no_implicit_conversion(VALUE val, const char *tname));
void
rb_no_implicit_conversion(VALUE val, const char *tname)
{
type_err_raise(val, tname, "no implicit conversion of");
}
NORETURN(void rb_cant_convert(VALUE val, const char *tname));
void
rb_cant_convert(VALUE val, const char *tname)
{
type_err_raise(val, tname, "can't convert");
}
NORETURN(void rb_cant_convert_invalid_return(VALUE val, const char *tname, const char *method_name, VALUE ret));
void
rb_cant_convert_invalid_return(VALUE val, const char *tname, const char *method_name, VALUE ret)
{
const char *cname = type_err_cname(val);
rb_encoding *enc = rb_utf8_encoding();
if (cname) {
rb_enc_raise(
enc, rb_eTypeError, "can't convert %s into %s (%s#%s gives %s)",
cname, tname, cname, method_name, type_err_cname(ret));
}
VALUE klass = rb_obj_class(val);
const char *retname = type_err_cname(ret);
if (!retname) {
retname = rb_obj_classname(ret);
}
rb_enc_raise(
enc, rb_eTypeError, "can't convert %"PRIsVALUE" into %s (%"PRIsVALUE"#%s gives %s)",
klass, tname, klass, method_name, retname);
}
void
rb_invalid_str(const char *str, const char *type)
{
VALUE s = rb_str_new2(str);
rb_raise(rb_eArgError, "invalid value for %s: %+"PRIsVALUE, type, s);
}
/*
* call-seq:
* key_error.receiver -> object
*
* Return the receiver associated with this KeyError exception.
*/
static VALUE
key_err_receiver(VALUE self)
{
VALUE recv;
recv = rb_ivar_lookup(self, id_receiver, Qundef);
if (!UNDEF_P(recv)) return recv;
rb_raise(rb_eArgError, "no receiver is available");
}
/*
* call-seq:
* key_error.key -> object
*
* Return the key caused this KeyError exception.
*/
static VALUE
key_err_key(VALUE self)
{
VALUE key;
key = rb_ivar_lookup(self, id_key, Qundef);
if (!UNDEF_P(key)) return key;
rb_raise(rb_eArgError, "no key is available");
}
VALUE
rb_key_err_new(VALUE mesg, VALUE recv, VALUE key)
{
VALUE exc = rb_obj_alloc(rb_eKeyError);
rb_ivar_set(exc, id_mesg, mesg);
rb_ivar_set(exc, id_bt, Qnil);
rb_ivar_set(exc, id_key, key);
rb_ivar_set(exc, id_receiver, recv);
return exc;
}
/*
* call-seq:
* KeyError.new(message=nil, receiver: nil, key: nil) -> key_error
*
* Construct a new +KeyError+ exception with the given message,
* receiver and key.
*/
static VALUE
key_err_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE options;
rb_call_super(rb_scan_args(argc, argv, "01:", NULL, &options), argv);
if (!NIL_P(options)) {
ID keywords[2];
VALUE values[numberof(keywords)];
int i;
keywords[0] = id_receiver;
keywords[1] = id_key;
rb_get_kwargs(options, keywords, 0, numberof(values), values);
for (i = 0; i < numberof(values); ++i) {
if (!UNDEF_P(values[i])) {
rb_ivar_set(self, keywords[i], values[i]);
}
}
}
return self;
}
/*
* call-seq:
* no_matching_pattern_key_error.matchee -> object
*
* Return the matchee associated with this NoMatchingPatternKeyError exception.
*/
static VALUE
no_matching_pattern_key_err_matchee(VALUE self)
{
VALUE matchee;
matchee = rb_ivar_lookup(self, id_matchee, Qundef);
if (!UNDEF_P(matchee)) return matchee;
rb_raise(rb_eArgError, "no matchee is available");
}
/*
* call-seq:
* no_matching_pattern_key_error.key -> object
*
* Return the key caused this NoMatchingPatternKeyError exception.
*/
static VALUE
no_matching_pattern_key_err_key(VALUE self)
{
VALUE key;
key = rb_ivar_lookup(self, id_key, Qundef);
if (!UNDEF_P(key)) return key;
rb_raise(rb_eArgError, "no key is available");
}
/*
* call-seq:
* NoMatchingPatternKeyError.new(message=nil, matchee: nil, key: nil) -> no_matching_pattern_key_error
*
* Construct a new +NoMatchingPatternKeyError+ exception with the given message,
* matchee and key.
*/
static VALUE
no_matching_pattern_key_err_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE options;
rb_call_super(rb_scan_args(argc, argv, "01:", NULL, &options), argv);
if (!NIL_P(options)) {
ID keywords[2];
VALUE values[numberof(keywords)];
int i;
keywords[0] = id_matchee;
keywords[1] = id_key;
rb_get_kwargs(options, keywords, 0, numberof(values), values);
for (i = 0; i < numberof(values); ++i) {
if (!UNDEF_P(values[i])) {
rb_ivar_set(self, keywords[i], values[i]);
}
}
}
return self;
}
/*
* call-seq:
* SyntaxError.new([msg]) -> syntax_error
*
* Construct a SyntaxError exception.
*/
static VALUE
syntax_error_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE mesg;
if (argc == 0) {
mesg = rb_fstring_lit("compile error");
argc = 1;
argv = &mesg;
}
return rb_call_super(argc, argv);
}
static VALUE
syntax_error_with_path(VALUE exc, VALUE path, VALUE *mesg, rb_encoding *enc)
{
if (NIL_P(exc)) {
*mesg = rb_enc_str_new(0, 0, enc);
exc = rb_class_new_instance(1, mesg, rb_eSyntaxError);
rb_ivar_set(exc, id_i_path, path);
}
else {
VALUE old_path = rb_attr_get(exc, id_i_path);
if (old_path != path) {
if (rb_str_equal(path, old_path)) {
rb_raise(rb_eArgError, "SyntaxError#path changed: %+"PRIsVALUE" (%p->%p)",
old_path, (void *)old_path, (void *)path);
}
else {
rb_raise(rb_eArgError, "SyntaxError#path changed: %+"PRIsVALUE"(%s%s)->%+"PRIsVALUE"(%s)",
old_path, rb_enc_name(rb_enc_get(old_path)),
(FL_TEST(old_path, RSTRING_FSTR) ? ":FSTR" : ""),
path, rb_enc_name(rb_enc_get(path)));
}
}
VALUE s = *mesg = rb_attr_get(exc, idMesg);
if (RSTRING_LEN(s) > 0 && *(RSTRING_END(s)-1) != '\n')
rb_str_cat_cstr(s, "\n");
}
return exc;
}
/*
* Document-module: Errno
*
* When an operating system encounters an error,
* it typically reports the error as an integer error code:
*
* $ ls nosuch.txt
* ls: cannot access 'nosuch.txt': No such file or directory
* $ echo $? # Code for last error.
* 2
*
* When the Ruby interpreter interacts with the operating system
* and receives such an error code (e.g., +2+),
* it maps the code to a particular Ruby exception class (e.g., +Errno::ENOENT+):
*
* File.open('nosuch.txt')
* # => No such file or directory @ rb_sysopen - nosuch.txt (Errno::ENOENT)
*
* Each such class is:
*
* - A nested class in this module, +Errno+.
* - A subclass of class SystemCallError.
* - Associated with an error code.
*
* Thus:
*
* Errno::ENOENT.superclass # => SystemCallError
* Errno::ENOENT::Errno # => 2
*
* The names of nested classes are returned by method +Errno.constants+:
*
* Errno.constants.size # => 158
* Errno.constants.sort.take(5) # => [:E2BIG, :EACCES, :EADDRINUSE, :EADDRNOTAVAIL, :EADV]
*
* As seen above, the error code associated with each class
* is available as the value of a constant;
* the value for a particular class may vary among operating systems.
* If the class is not needed for the particular operating system,
* the value is zero:
*
* Errno::ENOENT::Errno # => 2
* Errno::ENOTCAPABLE::Errno # => 0
*
* Each class in Errno can be created with optional messages:
*
* Errno::EPIPE.new # => #<Errno::EPIPE: Broken pipe>
* Errno::EPIPE.new("foo") # => #<Errno::EPIPE: Broken pipe - foo>
* Errno::EPIPE.new("foo", "here") # => #<Errno::EPIPE: Broken pipe @ here - foo>
*
* See SystemCallError.new.
*/
static st_table *syserr_tbl;
void
rb_free_warning(void)
{
st_free_table(warning_categories.id2enum);
st_free_table(warning_categories.enum2id);
st_free_table(syserr_tbl);
}
static VALUE
setup_syserr(int n, const char *name)
{
VALUE error = rb_define_class_under(rb_mErrno, name, rb_eSystemCallError);
/* capture nonblock errnos for WaitReadable/WaitWritable subclasses */
switch (n) {
case EAGAIN:
rb_eEAGAIN = error;
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
break;
case EWOULDBLOCK:
#endif
rb_eEWOULDBLOCK = error;
break;
case EINPROGRESS:
rb_eEINPROGRESS = error;
break;
}
rb_define_const(error, "Errno", INT2NUM(n));
st_add_direct(syserr_tbl, n, (st_data_t)error);
return error;
}
static VALUE
set_syserr(int n, const char *name)
{
st_data_t error;
if (!st_lookup(syserr_tbl, n, &error)) {
return setup_syserr(n, name);
}
else {
VALUE errclass = (VALUE)error;
rb_define_const(rb_mErrno, name, errclass);
return errclass;
}
}
static VALUE
get_syserr(int n)
{
st_data_t error;
if (!st_lookup(syserr_tbl, n, &error)) {
char name[DECIMAL_SIZE_OF(n) + sizeof("E-")];
snprintf(name, sizeof(name), "E%03d", n);
return setup_syserr(n, name);
}
return (VALUE)error;
}
/*
* call-seq:
* SystemCallError.new(msg, errno = nil, func = nil) -> system_call_error_subclass
*
* If _errno_ corresponds to a known system error code, constructs the
* appropriate Errno class for that error, otherwise constructs a
* generic SystemCallError object. The error number is subsequently
* available via the #errno method.
*
* If only numeric object is given, it is treated as an Integer _errno_,
* and _msg_ is omitted, otherwise the first argument _msg_ is used as
* the additional error message.
*
* SystemCallError.new(Errno::EPIPE::Errno)
* #=> #<Errno::EPIPE: Broken pipe>
*
* SystemCallError.new("foo")
* #=> #<SystemCallError: unknown error - foo>
*
* SystemCallError.new("foo", Errno::EPIPE::Errno)
* #=> #<Errno::EPIPE: Broken pipe - foo>
*
* If _func_ is not +nil+, it is appended to the message with "<tt> @ </tt>".
*
* SystemCallError.new("foo", Errno::EPIPE::Errno, "here")
* #=> #<Errno::EPIPE: Broken pipe @ here - foo>
*
* A subclass of SystemCallError can also be instantiated via the
* +new+ method of the subclass. See Errno.
*/
static VALUE
syserr_initialize(int argc, VALUE *argv, VALUE self)
{
const char *err;
VALUE mesg, error, func, errmsg;
VALUE klass = rb_obj_class(self);
if (klass == rb_eSystemCallError) {
st_data_t data = (st_data_t)klass;
rb_scan_args(argc, argv, "12", &mesg, &error, &func);
if (argc == 1 && FIXNUM_P(mesg)) {
error = mesg; mesg = Qnil;
}
if (!NIL_P(error) && st_lookup(syserr_tbl, NUM2LONG(error), &data)) {
klass = (VALUE)data;
/* change class */
if (!RB_TYPE_P(self, T_OBJECT)) { /* insurance to avoid type crash */
rb_raise(rb_eTypeError, "invalid instance type");
}
RBASIC_SET_CLASS(self, klass);
}
}
else {
rb_scan_args(argc, argv, "02", &mesg, &func);
error = rb_const_get(klass, id_Errno);
}
if (!NIL_P(error)) err = strerror(NUM2INT(error));
else err = "unknown error";
errmsg = rb_enc_str_new_cstr(err, rb_locale_encoding());
if (!NIL_P(mesg)) {
VALUE str = StringValue(mesg);
if (!NIL_P(func)) rb_str_catf(errmsg, " @ %"PRIsVALUE, func);
rb_str_catf(errmsg, " - %"PRIsVALUE, str);
}
mesg = errmsg;
rb_call_super(1, &mesg);
rb_ivar_set(self, id_errno, error);
return self;
}
/*
* call-seq:
* system_call_error.errno -> integer
*
* Return this SystemCallError's error number.
*/
static VALUE
syserr_errno(VALUE self)
{
return rb_attr_get(self, id_errno);
}
/*
* call-seq:
* system_call_error === other -> true or false
*
* Return +true+ if the receiver is a generic +SystemCallError+, or
* if the error numbers +self+ and _other_ are the same.
*/
static VALUE
syserr_eqq(VALUE self, VALUE exc)
{
VALUE num, e;
if (!rb_obj_is_kind_of(exc, rb_eSystemCallError)) {
if (!rb_respond_to(exc, id_errno)) return Qfalse;
}
else if (self == rb_eSystemCallError) return Qtrue;
num = rb_attr_get(exc, id_errno);
if (NIL_P(num)) {
num = rb_funcallv(exc, id_errno, 0, 0);
}
e = rb_const_get(self, id_Errno);
return RBOOL(FIXNUM_P(num) ? num == e : rb_equal(num, e));
}
/*
* Document-class: StandardError
*
* The most standard error types are subclasses of StandardError. A
* rescue clause without an explicit Exception class will rescue all
* StandardErrors (and only those).
*
* def foo
* raise "Oups"
* end
* foo rescue "Hello" #=> "Hello"
*
* On the other hand:
*
* require 'does/not/exist' rescue "Hi"
*
* <em>raises the exception:</em>
*
* LoadError: no such file to load -- does/not/exist
*
*/
/*
* Document-class: SystemExit
*
* Raised by +exit+ to initiate the termination of the script.
*/
/*
* Document-class: SignalException
*
* Raised when a signal is received.
*
* begin
* Process.kill('HUP',Process.pid)
* sleep # wait for receiver to handle signal sent by Process.kill
* rescue SignalException => e
* puts "received Exception #{e}"
* end
*
* <em>produces:</em>
*
* received Exception SIGHUP
*/
/*
* Document-class: Interrupt
*
* Raised when the interrupt signal is received, typically because the
* user has pressed Control-C (on most posix platforms). As such, it is a
* subclass of +SignalException+.
*
* begin
* puts "Press ctrl-C when you get bored"
* loop {}
* rescue Interrupt => e
* puts "Note: You will typically use Signal.trap instead."
* end
*
* <em>produces:</em>
*
* Press ctrl-C when you get bored
*
* <em>then waits until it is interrupted with Control-C and then prints:</em>
*
* Note: You will typically use Signal.trap instead.
*/
/*
* Document-class: TypeError
*
* Raised when encountering an object that is not of the expected type.
*
* [1, 2, 3].first("two")
*
* <em>raises the exception:</em>
*
* TypeError: no implicit conversion of String into Integer
*
*/
/*
* Document-class: ArgumentError
*
* Raised when the arguments are wrong and there isn't a more specific
* Exception class.
*
* Ex: passing the wrong number of arguments
*
* [1, 2, 3].first(4, 5)
*
* <em>raises the exception:</em>
*
* ArgumentError: wrong number of arguments (given 2, expected 1)
*
* Ex: passing an argument that is not acceptable:
*
* [1, 2, 3].first(-4)
*
* <em>raises the exception:</em>
*
* ArgumentError: negative array size
*/
/*
* Document-class: IndexError
*
* Raised when the given index is invalid.
*
* a = [:foo, :bar]
* a.fetch(0) #=> :foo
* a[4] #=> nil
* a.fetch(4) #=> IndexError: index 4 outside of array bounds: -2...2
*
*/
/*
* Document-class: KeyError
*
* Raised when the specified key is not found. It is a subclass of
* IndexError.
*
* h = {"foo" => :bar}
* h.fetch("foo") #=> :bar
* h.fetch("baz") #=> KeyError: key not found: "baz"
*
*/
/*
* Document-class: RangeError
*
* Raised when a given numerical value is out of range.
*
* [1, 2, 3].drop(1 << 100)
*
* <em>raises the exception:</em>
*
* RangeError: bignum too big to convert into `long'
*/
/*
* Document-class: ScriptError
*
* ScriptError is the superclass for errors raised when a script
* can not be executed because of a +LoadError+,
* +NotImplementedError+ or a +SyntaxError+. Note these type of
* +ScriptErrors+ are not +StandardError+ and will not be
* rescued unless it is specified explicitly (or its ancestor
* +Exception+).
*/
/*
* Document-class: SyntaxError
*
* Raised when encountering Ruby code with an invalid syntax.
*
* eval("1+1=2")
*
* <em>raises the exception:</em>
*
* SyntaxError: (eval):1: syntax error, unexpected '=', expecting $end
*/
/*
* Document-class: LoadError
*
* Raised when a file required (a Ruby script, extension library, ...)
* fails to load.
*
* require 'this/file/does/not/exist'
*
* <em>raises the exception:</em>
*
* LoadError: no such file to load -- this/file/does/not/exist
*/
/*
* Document-class: NotImplementedError
*
* Raised when a feature is not implemented on the current platform. For
* example, methods depending on the +fsync+ or +fork+ system calls may
* raise this exception if the underlying operating system or Ruby
* runtime does not support them.
*
* Note that if +fork+ raises a +NotImplementedError+, then
* <code>respond_to?(:fork)</code> returns +false+.
*/
/*
* Document-class: NameError
*
* Raised when a given name is invalid or undefined.
*
* puts foo
*
* <em>raises the exception:</em>
*
* NameError: undefined local variable or method `foo' for main:Object
*
* Since constant names must start with a capital:
*
* Integer.const_set :answer, 42
*
* <em>raises the exception:</em>
*
* NameError: wrong constant name answer
*/
/*
* Document-class: NoMethodError
*
* Raised when a method is called on a receiver which doesn't have it
* defined and also fails to respond with +method_missing+.
*
* "hello".to_ary
*
* <em>raises the exception:</em>
*
* NoMethodError: undefined method `to_ary' for an instance of String
*/
/*
* Document-class: FrozenError
*
* Raised when there is an attempt to modify a frozen object.
*
* [1, 2, 3].freeze << 4
*
* <em>raises the exception:</em>
*
* FrozenError: can't modify frozen Array
*/
/*
* Document-class: RuntimeError
*
* A generic error class raised when an invalid operation is attempted.
* Kernel#raise will raise a RuntimeError if no Exception class is
* specified.
*
* raise "ouch"
*
* <em>raises the exception:</em>
*
* RuntimeError: ouch
*/
/*
* Document-class: SecurityError
*
* No longer used by internal code.
*/
/*
* Document-class: NoMemoryError
*
* Raised when memory allocation fails.
*/
/*
* Document-class: SystemCallError
*
* SystemCallError is the base class for all low-level
* platform-dependent errors.
*
* The errors available on the current platform are subclasses of
* SystemCallError and are defined in the Errno module.
*
* File.open("does/not/exist")
*
* <em>raises the exception:</em>
*
* Errno::ENOENT: No such file or directory - does/not/exist
*/
/*
* Document-class: EncodingError
*
* EncodingError is the base class for encoding errors.
*/
/*
* Document-class: Encoding::CompatibilityError
*
* Raised by Encoding and String methods when the source encoding is
* incompatible with the target encoding.
*/
/*
* Document-class: NoMatchingPatternError
*
* Raised when matching pattern not found.
*/
/*
* Document-class: NoMatchingPatternKeyError
*
* Raised when matching key not found.
*/
/*
* Document-class: fatal
*
* +fatal+ is an Exception that is raised when Ruby has encountered a fatal
* error and must exit.
*/
/*
* Document-class: NameError::message
* :nodoc:
*/
/*
* Document-class: Exception
*
* Class +Exception+ and its subclasses are used to indicate that an error
* or other problem has occurred,
* and may need to be handled.
* See {Exceptions}[rdoc-ref:exceptions.md].
*
* An +Exception+ object carries certain information:
*
* - The type (the exception's class),
* commonly StandardError, RuntimeError, or a subclass of one or the other;
* see {Built-In Exception Class Hierarchy}[rdoc-ref:Exception@Built-In+Exception+Class+Hierarchy].
* - An optional descriptive message;
* see methods ::new, #message.
* - Optional backtrace information;
* see methods #backtrace, #backtrace_locations, #set_backtrace.
* - An optional cause;
* see method #cause.
*
* == Built-In \Exception Class Hierarchy
*
* The hierarchy of built-in subclasses of class +Exception+:
*
* * NoMemoryError
* * ScriptError
* * LoadError
* * NotImplementedError
* * SyntaxError
* * SecurityError
* * SignalException
* * Interrupt
* * StandardError
* * ArgumentError
* * UncaughtThrowError
* * EncodingError
* * FiberError
* * IOError
* * EOFError
* * IndexError
* * KeyError
* * StopIteration
* * ClosedQueueError
* * LocalJumpError
* * NameError
* * NoMethodError
* * RangeError
* * FloatDomainError
* * RegexpError
* * RuntimeError
* * FrozenError
* * SystemCallError
* * Errno (and its subclasses, representing system errors)
* * ThreadError
* * TypeError
* * ZeroDivisionError
* * SystemExit
* * SystemStackError
* * {fatal}[rdoc-ref:fatal]
*
*/
static VALUE
exception_alloc(VALUE klass)
{
return rb_class_allocate_instance(klass);
}
static VALUE
exception_dumper(VALUE exc)
{
// TODO: Currently, the instance variables "bt" and "bt_locations"
// refers to the same object (Array of String). But "bt_locations"
// should have an Array of Thread::Backtrace::Locations.
return exc;
}
static int
ivar_copy_i(ID key, VALUE val, st_data_t exc)
{
rb_ivar_set((VALUE)exc, key, val);
return ST_CONTINUE;
}
void rb_exc_check_circular_cause(VALUE exc);
static VALUE
exception_loader(VALUE exc, VALUE obj)
{
// The loader function of rb_marshal_define_compat seems to be called for two events:
// one is for fixup (r_fixup_compat), the other is for TYPE_USERDEF.
// In the former case, the first argument is an instance of Exception (because
// we pass rb_eException to rb_marshal_define_compat). In the latter case, the first
// argument is a class object (see TYPE_USERDEF case in r_object0).
// We want to copy all instance variables (but "bt_locations") from obj to exc.
// But we do not want to do so in the second case, so the following branch is for that.
if (RB_TYPE_P(exc, T_CLASS)) return obj; // maybe called from Marshal's TYPE_USERDEF
rb_ivar_foreach(obj, ivar_copy_i, exc);
rb_exc_check_circular_cause(exc);
if (rb_attr_get(exc, id_bt) == rb_attr_get(exc, id_bt_locations)) {
rb_ivar_set(exc, id_bt_locations, Qnil);
}
return exc;
}
void
Init_Exception(void)
{
rb_eException = rb_define_class("Exception", rb_cObject);
rb_define_alloc_func(rb_eException, exception_alloc);
rb_marshal_define_compat(rb_eException, rb_eException, exception_dumper, exception_loader);
rb_define_singleton_method(rb_eException, "exception", rb_class_new_instance, -1);
rb_define_singleton_method(rb_eException, "to_tty?", exc_s_to_tty_p, 0);
rb_define_method(rb_eException, "exception", exc_exception, -1);
rb_define_method(rb_eException, "initialize", exc_initialize, -1);
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, "detailed_message", exc_detailed_message, -1);
rb_define_method(rb_eException, "full_message", exc_full_message, -1);
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);
rb_define_method(rb_eException, "set_backtrace", exc_set_backtrace, 1);
rb_define_method(rb_eException, "cause", exc_cause, 0);
rb_eSystemExit = rb_define_class("SystemExit", rb_eException);
rb_define_method(rb_eSystemExit, "initialize", exit_initialize, -1);
rb_define_method(rb_eSystemExit, "status", exit_status, 0);
rb_define_method(rb_eSystemExit, "success?", exit_success_p, 0);
rb_eFatal = rb_define_class("fatal", rb_eException);
rb_eSignal = rb_define_class("SignalException", rb_eException);
rb_eInterrupt = rb_define_class("Interrupt", rb_eSignal);
rb_eStandardError = rb_define_class("StandardError", rb_eException);
rb_eTypeError = rb_define_class("TypeError", rb_eStandardError);
rb_eArgError = rb_define_class("ArgumentError", rb_eStandardError);
rb_eIndexError = rb_define_class("IndexError", rb_eStandardError);
rb_eKeyError = rb_define_class("KeyError", rb_eIndexError);
rb_define_method(rb_eKeyError, "initialize", key_err_initialize, -1);
rb_define_method(rb_eKeyError, "receiver", key_err_receiver, 0);
rb_define_method(rb_eKeyError, "key", key_err_key, 0);
rb_eRangeError = rb_define_class("RangeError", rb_eStandardError);
rb_eScriptError = rb_define_class("ScriptError", rb_eException);
rb_eSyntaxError = rb_define_class("SyntaxError", rb_eScriptError);
rb_define_method(rb_eSyntaxError, "initialize", syntax_error_initialize, -1);
/* RDoc will use literal name value while parsing rb_attr,
* and will render `idPath` as an attribute name without this trick */
ID path = idPath;
/* the path that failed to parse */
rb_attr(rb_eSyntaxError, path, TRUE, FALSE, FALSE);
rb_eLoadError = rb_define_class("LoadError", rb_eScriptError);
/* the path that failed to load */
rb_attr(rb_eLoadError, path, TRUE, FALSE, FALSE);
rb_eNotImpError = rb_define_class("NotImplementedError", rb_eScriptError);
rb_eNameError = rb_define_class("NameError", rb_eStandardError);
rb_define_method(rb_eNameError, "initialize", name_err_initialize, -1);
rb_define_method(rb_eNameError, "name", name_err_name, 0);
rb_define_method(rb_eNameError, "receiver", name_err_receiver, 0);
rb_define_method(rb_eNameError, "local_variables", name_err_local_variables, 0);
rb_cNameErrorMesg = rb_define_class_under(rb_eNameError, "message", rb_cObject);
rb_define_alloc_func(rb_cNameErrorMesg, name_err_mesg_alloc);
rb_define_method(rb_cNameErrorMesg, "initialize_copy", name_err_mesg_init_copy, 1);
rb_define_method(rb_cNameErrorMesg, "==", name_err_mesg_equal, 1);
rb_define_method(rb_cNameErrorMesg, "to_str", name_err_mesg_to_str, 0);
rb_define_method(rb_cNameErrorMesg, "_dump", name_err_mesg_dump, 1);
rb_define_singleton_method(rb_cNameErrorMesg, "_load", name_err_mesg_load, 1);
rb_eNoMethodError = rb_define_class("NoMethodError", rb_eNameError);
rb_define_method(rb_eNoMethodError, "initialize", nometh_err_initialize, -1);
rb_define_method(rb_eNoMethodError, "args", nometh_err_args, 0);
rb_define_method(rb_eNoMethodError, "private_call?", nometh_err_private_call_p, 0);
rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError);
rb_eFrozenError = rb_define_class("FrozenError", rb_eRuntimeError);
rb_define_method(rb_eFrozenError, "initialize", frozen_err_initialize, -1);
rb_define_method(rb_eFrozenError, "receiver", frozen_err_receiver, 0);
rb_eSecurityError = rb_define_class("SecurityError", rb_eException);
rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException);
rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError);
rb_eEncCompatError = rb_define_class_under(rb_cEncoding, "CompatibilityError", rb_eEncodingError);
rb_eNoMatchingPatternError = rb_define_class("NoMatchingPatternError", rb_eStandardError);
rb_eNoMatchingPatternKeyError = rb_define_class("NoMatchingPatternKeyError", rb_eNoMatchingPatternError);
rb_define_method(rb_eNoMatchingPatternKeyError, "initialize", no_matching_pattern_key_err_initialize, -1);
rb_define_method(rb_eNoMatchingPatternKeyError, "matchee", no_matching_pattern_key_err_matchee, 0);
rb_define_method(rb_eNoMatchingPatternKeyError, "key", no_matching_pattern_key_err_key, 0);
syserr_tbl = st_init_numtable();
rb_eSystemCallError = rb_define_class("SystemCallError", rb_eStandardError);
rb_define_method(rb_eSystemCallError, "initialize", syserr_initialize, -1);
rb_define_method(rb_eSystemCallError, "errno", syserr_errno, 0);
rb_define_singleton_method(rb_eSystemCallError, "===", syserr_eqq, 1);
rb_mErrno = rb_define_module("Errno");
rb_mWarning = rb_define_module("Warning");
rb_define_singleton_method(rb_mWarning, "[]", rb_warning_s_aref, 1);
rb_define_singleton_method(rb_mWarning, "[]=", rb_warning_s_aset, 2);
rb_define_singleton_method(rb_mWarning, "categories", rb_warning_s_categories, 0);
rb_define_method(rb_mWarning, "warn", rb_warning_s_warn, -1);
rb_extend_object(rb_mWarning, rb_mWarning);
/* :nodoc: */
rb_cWarningBuffer = rb_define_class_under(rb_mWarning, "buffer", rb_cString);
rb_define_method(rb_cWarningBuffer, "write", warning_write, -1);
id_cause = rb_intern_const("cause");
id_message = rb_intern_const("message");
id_detailed_message = rb_intern_const("detailed_message");
id_backtrace = rb_intern_const("backtrace");
id_key = rb_intern_const("key");
id_matchee = rb_intern_const("matchee");
id_args = rb_intern_const("args");
id_receiver = rb_intern_const("receiver");
id_private_call_p = rb_intern_const("private_call?");
id_local_variables = rb_intern_const("local_variables");
id_Errno = rb_intern_const("Errno");
id_errno = rb_intern_const("errno");
id_i_path = rb_intern_const("@path");
id_warn = rb_intern_const("warn");
id_category = rb_intern_const("category");
id_deprecated = rb_intern_const("deprecated");
id_experimental = rb_intern_const("experimental");
id_performance = rb_intern_const("performance");
id_strict_unused_block = rb_intern_const("strict_unused_block");
id_top = rb_intern_const("top");
id_bottom = rb_intern_const("bottom");
id_iseq = rb_make_internal_id();
id_recv = rb_make_internal_id();
sym_category = ID2SYM(id_category);
sym_highlight = ID2SYM(rb_intern_const("highlight"));
warning_categories.id2enum = rb_init_identtable();
st_add_direct(warning_categories.id2enum, id_deprecated, RB_WARN_CATEGORY_DEPRECATED);
st_add_direct(warning_categories.id2enum, id_experimental, RB_WARN_CATEGORY_EXPERIMENTAL);
st_add_direct(warning_categories.id2enum, id_performance, RB_WARN_CATEGORY_PERFORMANCE);
st_add_direct(warning_categories.id2enum, id_strict_unused_block, RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK);
warning_categories.enum2id = rb_init_identtable();
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_NONE, 0);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_DEPRECATED, id_deprecated);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_EXPERIMENTAL, id_experimental);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_PERFORMANCE, id_performance);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK, id_strict_unused_block);
}
void
rb_enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...)
{
va_list args;
VALUE mesg;
va_start(args, fmt);
mesg = rb_enc_vsprintf(enc, fmt, args);
va_end(args);
rb_exc_raise(rb_exc_new3(exc, mesg));
}
void
rb_vraise(VALUE exc, const char *fmt, va_list ap)
{
rb_exc_raise(rb_exc_new3(exc, rb_vsprintf(fmt, ap)));
}
void
rb_raise(VALUE exc_class, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
VALUE exc = rb_exc_new3(exc_class, rb_vsprintf(fmt, args));
va_end(args);
rb_exc_raise(exc);
}
NORETURN(static void raise_loaderror(VALUE path, VALUE mesg));
static void
raise_loaderror(VALUE path, VALUE mesg)
{
VALUE err = rb_exc_new3(rb_eLoadError, mesg);
rb_ivar_set(err, id_i_path, path);
rb_exc_raise(err);
}
void
rb_loaderror(const char *fmt, ...)
{
va_list args;
VALUE mesg;
va_start(args, fmt);
mesg = rb_enc_vsprintf(rb_locale_encoding(), fmt, args);
va_end(args);
raise_loaderror(Qnil, mesg);
}
void
rb_loaderror_with_path(VALUE path, const char *fmt, ...)
{
va_list args;
VALUE mesg;
va_start(args, fmt);
mesg = rb_enc_vsprintf(rb_locale_encoding(), fmt, args);
va_end(args);
raise_loaderror(path, mesg);
}
void
rb_notimplement(void)
{
rb_raise(rb_eNotImpError,
"%"PRIsVALUE"() function is unimplemented on this machine",
rb_id2str(rb_frame_this_func()));
}
void
rb_fatal(const char *fmt, ...)
{
va_list args;
VALUE mesg;
if (! ruby_thread_has_gvl_p()) {
/* The thread has no GVL. Object allocation impossible (cant run GC),
* thus no message can be printed out. */
fprintf(stderr, "[FATAL] rb_fatal() outside of GVL\n");
rb_print_backtrace(stderr);
die();
}
va_start(args, fmt);
mesg = rb_vsprintf(fmt, args);
va_end(args);
rb_exc_fatal(rb_exc_new3(rb_eFatal, mesg));
}
static VALUE
make_errno_exc(const char *mesg)
{
int n = errno;
errno = 0;
if (n == 0) {
rb_bug("rb_sys_fail(%s) - errno == 0", mesg ? mesg : "");
}
return rb_syserr_new(n, mesg);
}
static VALUE
make_errno_exc_str(VALUE mesg)
{
int n = errno;
errno = 0;
if (!mesg) mesg = Qnil;
if (n == 0) {
const char *s = !NIL_P(mesg) ? RSTRING_PTR(mesg) : "";
rb_bug("rb_sys_fail_str(%s) - errno == 0", s);
}
return rb_syserr_new_str(n, mesg);
}
VALUE
rb_syserr_new(int n, const char *mesg)
{
VALUE arg;
arg = mesg ? rb_str_new2(mesg) : Qnil;
return rb_syserr_new_str(n, arg);
}
VALUE
rb_syserr_new_str(int n, VALUE arg)
{
return rb_class_new_instance(1, &arg, get_syserr(n));
}
void
rb_syserr_fail(int e, const char *mesg)
{
rb_exc_raise(rb_syserr_new(e, mesg));
}
void
rb_syserr_fail_str(int e, VALUE mesg)
{
rb_exc_raise(rb_syserr_new_str(e, mesg));
}
#undef rb_sys_fail
void
rb_sys_fail(const char *mesg)
{
rb_exc_raise(make_errno_exc(mesg));
}
#undef rb_sys_fail_str
void
rb_sys_fail_str(VALUE mesg)
{
rb_exc_raise(make_errno_exc_str(mesg));
}
#ifdef RUBY_FUNCTION_NAME_STRING
void
rb_sys_fail_path_in(const char *func_name, VALUE path)
{
int n = errno;
errno = 0;
rb_syserr_fail_path_in(func_name, n, path);
}
void
rb_syserr_fail_path_in(const char *func_name, int n, VALUE path)
{
rb_exc_raise(rb_syserr_new_path_in(func_name, n, path));
}
VALUE
rb_syserr_new_path_in(const char *func_name, int n, VALUE path)
{
VALUE args[2];
if (!path) path = Qnil;
if (n == 0) {
const char *s = !NIL_P(path) ? RSTRING_PTR(path) : "";
if (!func_name) func_name = "(null)";
rb_bug("rb_sys_fail_path_in(%s, %s) - errno == 0",
func_name, s);
}
args[0] = path;
args[1] = rb_str_new_cstr(func_name);
return rb_class_new_instance(2, args, get_syserr(n));
}
#endif
NORETURN(static void rb_mod_exc_raise(VALUE exc, VALUE mod));
static void
rb_mod_exc_raise(VALUE exc, VALUE mod)
{
rb_extend_object(exc, mod);
rb_exc_raise(exc);
}
void
rb_mod_sys_fail(VALUE mod, const char *mesg)
{
VALUE exc = make_errno_exc(mesg);
rb_mod_exc_raise(exc, mod);
}
void
rb_mod_sys_fail_str(VALUE mod, VALUE mesg)
{
VALUE exc = make_errno_exc_str(mesg);
rb_mod_exc_raise(exc, mod);
}
void
rb_mod_syserr_fail(VALUE mod, int e, const char *mesg)
{
VALUE exc = rb_syserr_new(e, mesg);
rb_mod_exc_raise(exc, mod);
}
void
rb_mod_syserr_fail_str(VALUE mod, int e, VALUE mesg)
{
VALUE exc = rb_syserr_new_str(e, mesg);
rb_mod_exc_raise(exc, mod);
}
static void
syserr_warning(VALUE mesg, int err)
{
rb_str_set_len(mesg, RSTRING_LEN(mesg)-1);
rb_str_catf(mesg, ": %s\n", strerror(err));
rb_write_warning_str(mesg);
}
#if 0
void
rb_sys_warn(const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
int errno_save = errno;
with_warning_string(mesg, 0, fmt) {
syserr_warning(mesg, errno_save);
}
errno = errno_save;
}
}
void
rb_syserr_warn(int err, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
syserr_warning(mesg, err);
}
}
}
void
rb_sys_enc_warn(rb_encoding *enc, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
int errno_save = errno;
with_warning_string(mesg, enc, fmt) {
syserr_warning(mesg, errno_save);
}
errno = errno_save;
}
}
void
rb_syserr_enc_warn(int err, rb_encoding *enc, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warning_string(mesg, enc, fmt) {
syserr_warning(mesg, err);
}
}
}
#endif
void
rb_sys_warning(const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
int errno_save = errno;
with_warning_string(mesg, 0, fmt) {
syserr_warning(mesg, errno_save);
}
errno = errno_save;
}
}
#if 0
void
rb_syserr_warning(int err, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
syserr_warning(mesg, err);
}
}
}
#endif
void
rb_sys_enc_warning(rb_encoding *enc, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
int errno_save = errno;
with_warning_string(mesg, enc, fmt) {
syserr_warning(mesg, errno_save);
}
errno = errno_save;
}
}
void
rb_syserr_enc_warning(int err, rb_encoding *enc, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warning_string(mesg, enc, fmt) {
syserr_warning(mesg, err);
}
}
}
void
rb_load_fail(VALUE path, const char *err)
{
VALUE mesg = rb_str_buf_new_cstr(err);
rb_str_cat2(mesg, " -- ");
rb_str_append(mesg, path); /* should be ASCII compatible */
raise_loaderror(path, mesg);
}
void
rb_error_frozen(const char *what)
{
rb_raise(rb_eFrozenError, "can't modify frozen %s", what);
}
void
rb_frozen_error_raise(VALUE frozen_obj, const char *fmt, ...)
{
va_list args;
VALUE exc, mesg;
va_start(args, fmt);
mesg = rb_vsprintf(fmt, args);
va_end(args);
exc = rb_exc_new3(rb_eFrozenError, mesg);
rb_ivar_set(exc, id_recv, frozen_obj);
rb_exc_raise(exc);
}
static VALUE
inspect_frozen_obj(VALUE obj, VALUE mesg, int recur)
{
if (recur) {
rb_str_cat_cstr(mesg, " ...");
}
else {
rb_str_append(mesg, rb_inspect(obj));
}
return mesg;
}
static VALUE
get_created_info(VALUE obj, int *pline)
{
VALUE info = rb_attr_get(obj, id_debug_created_info);
if (NIL_P(info)) return Qnil;
VALUE path = rb_ary_entry(info, 0);
VALUE line = rb_ary_entry(info, 1);
if (NIL_P(path)) return Qnil;
*pline = NUM2INT(line);
return StringValue(path);
}
void
rb_error_frozen_object(VALUE frozen_obj)
{
rb_yjit_lazy_push_frame(GET_EC()->cfp->pc);
VALUE mesg = rb_sprintf("can't modify frozen %"PRIsVALUE": ",
rb_obj_class(frozen_obj));
VALUE exc = rb_exc_new_str(rb_eFrozenError, mesg);
rb_ivar_set(exc, id_recv, frozen_obj);
rb_exec_recursive(inspect_frozen_obj, frozen_obj, mesg);
int created_line;
VALUE created_path = get_created_info(frozen_obj, &created_line);
if (!NIL_P(created_path)) {
rb_str_catf(mesg, ", created at %"PRIsVALUE":%d", created_path, created_line);
}
rb_exc_raise(exc);
}
void
rb_warn_unchilled_literal(VALUE obj)
{
rb_warning_category_t category = RB_WARN_CATEGORY_DEPRECATED;
if (!NIL_P(ruby_verbose) && rb_warning_category_enabled_p(category)) {
int line;
VALUE file = rb_source_location(&line);
VALUE mesg = NIL_P(file) ? rb_str_new(0, 0) : rb_str_dup(file);
if (!NIL_P(file)) {
if (line) rb_str_catf(mesg, ":%d", line);
rb_str_cat2(mesg, ": ");
}
rb_str_cat2(mesg, "warning: literal string will be frozen in the future");
VALUE str = obj;
if (STR_SHARED_P(str)) {
str = RSTRING(obj)->as.heap.aux.shared;
}
VALUE created = get_created_info(str, &line);
if (NIL_P(created)) {
rb_str_cat2(mesg, " (run with --debug-frozen-string-literal for more information)\n");
}
else {
rb_str_cat2(mesg, "\n");
rb_str_append(mesg, created);
if (line) rb_str_catf(mesg, ":%d", line);
rb_str_cat2(mesg, ": info: the string was created here\n");
}
rb_warn_category(mesg, rb_warning_category_to_name(category));
}
}
#undef rb_check_frozen
void
rb_check_frozen(VALUE obj)
{
rb_check_frozen_inline(obj);
}
void
rb_check_copyable(VALUE obj, VALUE orig)
{
if (!FL_ABLE(obj)) return;
rb_check_frozen(obj);
if (!FL_ABLE(orig)) return;
}
void
Init_syserr(void)
{
rb_eNOERROR = setup_syserr(0, "NOERROR");
#if 0
/* No error */
rb_define_const(rb_mErrno, "NOERROR", rb_eNOERROR);
#endif
#define defined_error(name, num) set_syserr((num), (name));
#define undefined_error(name) rb_define_const(rb_mErrno, (name), rb_eNOERROR);
#include "known_errors.inc"
#undef defined_error
#undef undefined_error
}
#include "warning.rbinc"
/*!
* \}
*/