mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
This change separates the master box from the root box, as the single master copy of boxes. Before this change, the root box is the source of copies, and also it runs builtin classes' code. Builtin code makes changes by requiring files, thus the source of copies is mutable, be different from the past one. This causes different internal states of boxes, depending on when it is created. After this change, all boxes are created from the master box, and the master box never runs code, so it realizes the immutable source of copies. This also makes is possible to separate RubyGems (and other default gems) from each boxes. All boxes will have their own copies of RubyGems. RubyGems has its internal state, but it'll be separated between boxes after this change.
1301 lines
34 KiB
C
1301 lines
34 KiB
C
/* indent-tabs-mode: nil */
|
|
|
|
#include "eval_intern.h"
|
|
#include "internal.h"
|
|
#include "internal/box.h"
|
|
#include "internal/class.h"
|
|
#include "internal/eval.h"
|
|
#include "internal/error.h"
|
|
#include "internal/file.h"
|
|
#include "internal/gc.h"
|
|
#include "internal/hash.h"
|
|
#include "internal/io.h"
|
|
#include "internal/load.h"
|
|
#include "internal/st.h"
|
|
#include "internal/variable.h"
|
|
#include "iseq.h"
|
|
#include "ruby/internal/globals.h"
|
|
#include "ruby/util.h"
|
|
#include "vm_core.h"
|
|
#include "darray.h"
|
|
#include "zjit.h"
|
|
|
|
#include "builtin.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_SENDFILE_H
|
|
# include <sys/sendfile.h>
|
|
#endif
|
|
#ifdef HAVE_COPYFILE_H
|
|
#include <copyfile.h>
|
|
#endif
|
|
|
|
VALUE rb_cBox = 0;
|
|
VALUE rb_cBoxEntry = 0;
|
|
VALUE rb_mBoxLoader = 0;
|
|
|
|
static rb_box_t master_box[1]; /* Initialize in initialize_master_box() */
|
|
static rb_box_t *root_box;
|
|
static rb_box_t *main_box;
|
|
|
|
static rb_box_gem_flags_t box_gem_flags[1];
|
|
|
|
static char *tmp_dir;
|
|
static bool tmp_dir_has_dirsep;
|
|
|
|
#define BOX_TMP_PREFIX "_ruby_box_"
|
|
|
|
#ifndef MAXPATHLEN
|
|
# define MAXPATHLEN 1024
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
# define DIRSEP "\\"
|
|
#else
|
|
# define DIRSEP "/"
|
|
#endif
|
|
|
|
bool ruby_box_enabled = false; // extern
|
|
bool ruby_box_init_done = false; // extern
|
|
bool ruby_box_crashed = false; // extern, changed only in vm.c
|
|
|
|
VALUE rb_resolve_feature_path(VALUE klass, VALUE fname);
|
|
static VALUE rb_box_inspect(VALUE obj);
|
|
static void cleanup_all_local_extensions(VALUE libmap);
|
|
|
|
void
|
|
rb_box_set_gem_flags(rb_box_gem_flags_t *flags)
|
|
{
|
|
|
|
box_gem_flags->gem = flags->gem;
|
|
box_gem_flags->error_highlight = flags->error_highlight;
|
|
box_gem_flags->did_you_mean = flags->did_you_mean;
|
|
box_gem_flags->syntax_suggest = flags->syntax_suggest;
|
|
}
|
|
|
|
void
|
|
rb_box_init_done(void)
|
|
{
|
|
ruby_box_init_done = true;
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_master_box(void)
|
|
{
|
|
return master_box;
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_root_box(void)
|
|
{
|
|
if (!root_box) // The root box isn't initialized yet - The Ruby runtime is in setup.
|
|
return master_box;
|
|
return root_box;
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_main_box(void)
|
|
{
|
|
return main_box;
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_current_box(void)
|
|
{
|
|
/*
|
|
* If RUBY_BOX is not set, the master box is the only available one.
|
|
*
|
|
* While the root/main boxes are not initialized, the master box is
|
|
* the only valid box.
|
|
* This early return is to avoid accessing EC before its setup.
|
|
*/
|
|
if (!root_box)
|
|
return master_box;
|
|
if (!main_box)
|
|
return root_box;
|
|
|
|
return rb_vm_current_box(GET_EC());
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_loading_box(void)
|
|
{
|
|
if (!root_box)
|
|
return master_box;
|
|
if (!main_box)
|
|
return root_box;
|
|
|
|
return rb_vm_loading_box(GET_EC());
|
|
}
|
|
|
|
const rb_box_t *
|
|
rb_current_box_in_crash_report(void)
|
|
{
|
|
if (ruby_box_crashed)
|
|
return NULL;
|
|
return rb_current_box();
|
|
}
|
|
|
|
static long box_id_counter = 0;
|
|
|
|
static long
|
|
box_generate_id(void)
|
|
{
|
|
long id;
|
|
RB_VM_LOCKING() {
|
|
id = ++box_id_counter;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
static VALUE
|
|
box_main_to_s(VALUE obj)
|
|
{
|
|
return rb_str_new2("main");
|
|
}
|
|
|
|
static void
|
|
box_entry_initialize(rb_box_t *box)
|
|
{
|
|
const rb_box_t *master = rb_master_box();
|
|
|
|
// These will be updated immediately
|
|
box->box_object = 0;
|
|
box->box_id = 0;
|
|
|
|
box->top_self = rb_obj_alloc(rb_cObject);
|
|
rb_define_singleton_method(box->top_self, "to_s", box_main_to_s, 0);
|
|
rb_define_alias(rb_singleton_class(box->top_self), "inspect", "to_s");
|
|
box->load_path = rb_ary_dup(master->load_path);
|
|
box->expanded_load_path = rb_ary_dup(master->expanded_load_path);
|
|
box->load_path_snapshot = rb_ary_new();
|
|
box->load_path_check_cache = 0;
|
|
box->loaded_features = rb_ary_dup(master->loaded_features);
|
|
box->loaded_features_snapshot = rb_ary_new();
|
|
box->loaded_features_index = st_init_numtable();
|
|
box->loaded_features_realpaths = rb_hash_dup(master->loaded_features_realpaths);
|
|
box->loaded_features_realpath_map = rb_hash_dup(master->loaded_features_realpath_map);
|
|
box->loading_table = st_init_strtable();
|
|
box->ruby_dln_libmap = rb_hash_new_with_size(0);
|
|
box->gvar_tbl = rb_hash_new_with_size(0);
|
|
box->classext_cow_classes = st_init_numtable();
|
|
|
|
box->is_user = true;
|
|
box->is_optional = true;
|
|
}
|
|
|
|
void
|
|
rb_box_gc_update_references(void *ptr)
|
|
{
|
|
rb_box_t *box = (rb_box_t *)ptr;
|
|
if (!box) return;
|
|
|
|
if (box->box_object)
|
|
box->box_object = rb_gc_location(box->box_object);
|
|
if (box->top_self)
|
|
box->top_self = rb_gc_location(box->top_self);
|
|
box->load_path = rb_gc_location(box->load_path);
|
|
box->expanded_load_path = rb_gc_location(box->expanded_load_path);
|
|
box->load_path_snapshot = rb_gc_location(box->load_path_snapshot);
|
|
if (box->load_path_check_cache) {
|
|
box->load_path_check_cache = rb_gc_location(box->load_path_check_cache);
|
|
}
|
|
box->loaded_features = rb_gc_location(box->loaded_features);
|
|
box->loaded_features_snapshot = rb_gc_location(box->loaded_features_snapshot);
|
|
box->loaded_features_realpaths = rb_gc_location(box->loaded_features_realpaths);
|
|
box->loaded_features_realpath_map = rb_gc_location(box->loaded_features_realpath_map);
|
|
box->ruby_dln_libmap = rb_gc_location(box->ruby_dln_libmap);
|
|
box->gvar_tbl = rb_gc_location(box->gvar_tbl);
|
|
}
|
|
|
|
void
|
|
rb_box_entry_mark(void *ptr)
|
|
{
|
|
const rb_box_t *box = (rb_box_t *)ptr;
|
|
if (!box) return;
|
|
|
|
rb_gc_mark(box->box_object);
|
|
rb_gc_mark(box->top_self);
|
|
rb_gc_mark(box->load_path);
|
|
rb_gc_mark(box->expanded_load_path);
|
|
rb_gc_mark(box->load_path_snapshot);
|
|
rb_gc_mark(box->load_path_check_cache);
|
|
rb_gc_mark(box->loaded_features);
|
|
rb_gc_mark(box->loaded_features_snapshot);
|
|
rb_gc_mark(box->loaded_features_realpaths);
|
|
rb_gc_mark(box->loaded_features_realpath_map);
|
|
if (box->loading_table) {
|
|
rb_mark_tbl(box->loading_table);
|
|
}
|
|
rb_gc_mark(box->ruby_dln_libmap);
|
|
rb_gc_mark(box->gvar_tbl);
|
|
if (box->classext_cow_classes) {
|
|
rb_mark_tbl(box->classext_cow_classes);
|
|
}
|
|
}
|
|
|
|
static int
|
|
free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg)
|
|
{
|
|
xfree((char *)key);
|
|
return ST_DELETE;
|
|
}
|
|
|
|
static int
|
|
free_loaded_feature_index_i(st_data_t key, st_data_t value, st_data_t arg)
|
|
{
|
|
if (!FIXNUM_P(value)) {
|
|
rb_darray_free_sized((void *)value, long);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
free_box_st_tables(void *ptr)
|
|
{
|
|
rb_box_t *box = (rb_box_t *)ptr;
|
|
if (box->loading_table) {
|
|
st_foreach(box->loading_table, free_loading_table_entry, 0);
|
|
st_free_table(box->loading_table);
|
|
box->loading_table = 0;
|
|
}
|
|
|
|
if (box->loaded_features_index) {
|
|
st_foreach(box->loaded_features_index, free_loaded_feature_index_i, 0);
|
|
st_free_table(box->loaded_features_index);
|
|
}
|
|
}
|
|
|
|
static int
|
|
free_classext_for_box(st_data_t _key, st_data_t obj_value, st_data_t box_arg)
|
|
{
|
|
rb_classext_t *ext;
|
|
VALUE obj = (VALUE)obj_value;
|
|
const rb_box_t *box = (const rb_box_t *)box_arg;
|
|
|
|
if (RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)) {
|
|
ext = rb_class_unlink_classext(obj, box);
|
|
rb_class_classext_free(obj, ext, false);
|
|
}
|
|
else if (RB_TYPE_P(obj, T_ICLASS)) {
|
|
ext = rb_class_unlink_classext(obj, box);
|
|
rb_iclass_classext_free(obj, ext, false);
|
|
}
|
|
else {
|
|
rb_bug("Invalid type of object in classext_cow_classes: %s", rb_type_str(BUILTIN_TYPE(obj)));
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
box_entry_free(void *ptr)
|
|
{
|
|
const rb_box_t *box = (const rb_box_t *)ptr;
|
|
|
|
if (box->classext_cow_classes) {
|
|
st_foreach(box->classext_cow_classes, free_classext_for_box, (st_data_t)box);
|
|
}
|
|
|
|
cleanup_all_local_extensions(box->ruby_dln_libmap);
|
|
|
|
free_box_st_tables(ptr);
|
|
SIZED_FREE(box);
|
|
}
|
|
|
|
static size_t
|
|
box_entry_memsize(const void *ptr)
|
|
{
|
|
size_t size = sizeof(rb_box_t);
|
|
const rb_box_t *box = (const rb_box_t *)ptr;
|
|
if (box->loaded_features_index) {
|
|
size += rb_st_memsize(box->loaded_features_index);
|
|
}
|
|
if (box->loading_table) {
|
|
size += rb_st_memsize(box->loading_table);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static const rb_data_type_t rb_box_data_type = {
|
|
"Ruby::Box::Entry",
|
|
{
|
|
rb_box_entry_mark,
|
|
box_entry_free,
|
|
box_entry_memsize,
|
|
rb_box_gc_update_references,
|
|
},
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers
|
|
};
|
|
|
|
static const rb_data_type_t rb_master_box_data_type = {
|
|
"Ruby::Box::Master",
|
|
{
|
|
rb_box_entry_mark,
|
|
free_box_st_tables,
|
|
box_entry_memsize,
|
|
rb_box_gc_update_references,
|
|
},
|
|
&rb_box_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers
|
|
};
|
|
|
|
VALUE
|
|
rb_box_entry_alloc(VALUE klass)
|
|
{
|
|
rb_box_t *entry;
|
|
VALUE obj = TypedData_Make_Struct(klass, rb_box_t, &rb_box_data_type, entry);
|
|
box_entry_initialize(entry);
|
|
return obj;
|
|
}
|
|
|
|
static rb_box_t *
|
|
get_box_struct_internal(VALUE entry)
|
|
{
|
|
rb_box_t *sval;
|
|
TypedData_Get_Struct(entry, rb_box_t, &rb_box_data_type, sval);
|
|
return sval;
|
|
}
|
|
|
|
rb_box_t *
|
|
rb_get_box_t(VALUE box)
|
|
{
|
|
VALUE entry;
|
|
ID id_box_entry;
|
|
|
|
VM_ASSERT(box);
|
|
|
|
if (NIL_P(box))
|
|
return (rb_box_t *)rb_root_box();
|
|
|
|
VM_ASSERT(BOX_OBJ_P(box));
|
|
|
|
CONST_ID(id_box_entry, "__box_entry__");
|
|
entry = rb_attr_get(box, id_box_entry);
|
|
return get_box_struct_internal(entry);
|
|
}
|
|
|
|
VALUE
|
|
rb_get_box_object(rb_box_t *box)
|
|
{
|
|
VM_ASSERT(box && box->box_object);
|
|
return box->box_object;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ruby::Box.new -> new_box
|
|
*
|
|
* Returns a new Ruby::Box object.
|
|
*/
|
|
static VALUE
|
|
box_initialize(VALUE box_value)
|
|
{
|
|
rb_box_t *box;
|
|
rb_classext_t *object_classext;
|
|
VALUE entry;
|
|
ID id_box_entry;
|
|
CONST_ID(id_box_entry, "__box_entry__");
|
|
|
|
if (!rb_box_available()) {
|
|
rb_raise(rb_eRuntimeError, "Ruby Box is disabled. Set RUBY_BOX=1 environment variable to use Ruby::Box.");
|
|
}
|
|
|
|
entry = rb_class_new_instance_pass_kw(0, NULL, rb_cBoxEntry);
|
|
box = get_box_struct_internal(entry);
|
|
|
|
box->box_object = box_value;
|
|
box->box_id = box_generate_id();
|
|
rb_define_singleton_method(box->load_path, "resolve_feature_path", rb_resolve_feature_path, 1);
|
|
|
|
// Set the Ruby::Box object unique/consistent from any boxes to have just single
|
|
// constant table from any view of every (including main) box.
|
|
// If a code in the box adds a constant, the constant will be visible even from root/main.
|
|
RCLASS_SET_PRIME_CLASSEXT_WRITABLE(box_value, true);
|
|
|
|
// Get a clean constant table of Object even by writable one
|
|
// because ns was just created, so it has not touched any constants yet.
|
|
object_classext = RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, box);
|
|
RCLASS_SET_CONST_TBL(box_value, RCLASSEXT_CONST_TBL(object_classext), true);
|
|
|
|
rb_ivar_set(box_value, id_box_entry, entry);
|
|
|
|
if (ruby_box_init_done) {
|
|
if (box_gem_flags->gem) {
|
|
rb_vm_call_cfunc_in_box(Qnil, rb_define_gem_modules, (VALUE)box_gem_flags, Qnil,
|
|
rb_str_new_cstr("before_prelude.user.dummy"), (const rb_box_t *)box);
|
|
rb_load_gem_prelude((VALUE)box);
|
|
}
|
|
}
|
|
|
|
// Invalidate ZJIT code that assumes only the root box is active
|
|
rb_zjit_invalidate_root_box();
|
|
|
|
return box_value;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ruby::Box.enabled? -> true or false
|
|
*
|
|
* Returns +true+ if Ruby::Box is enabled.
|
|
*/
|
|
static VALUE
|
|
rb_box_s_getenabled(VALUE recv)
|
|
{
|
|
return RBOOL(rb_box_available());
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ruby::Box.current -> box, nil or false
|
|
*
|
|
* Returns the current box.
|
|
* Returns +nil+ if Ruby Box is not enabled.
|
|
*/
|
|
static VALUE
|
|
rb_box_s_current(VALUE recv)
|
|
{
|
|
const rb_box_t *box;
|
|
|
|
if (!rb_box_available())
|
|
return Qnil;
|
|
|
|
box = rb_vm_current_box(GET_EC());
|
|
VM_ASSERT(box && box->box_object);
|
|
return box->box_object;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* load_path -> array
|
|
*
|
|
* Returns box local load path.
|
|
*/
|
|
static VALUE
|
|
rb_box_load_path(VALUE box)
|
|
{
|
|
VM_ASSERT(BOX_OBJ_P(box));
|
|
return rb_get_box_t(box)->load_path;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
UINT rb_w32_system_tmpdir(WCHAR *path, UINT len);
|
|
#endif
|
|
|
|
/* Copied from mjit.c Ruby 3.0.3 */
|
|
static char *
|
|
system_default_tmpdir(void)
|
|
{
|
|
// c.f. ext/etc/etc.c:etc_systmpdir()
|
|
#ifdef _WIN32
|
|
WCHAR tmppath[_MAX_PATH];
|
|
UINT len = rb_w32_system_tmpdir(tmppath, numberof(tmppath));
|
|
if (len) {
|
|
int blen = WideCharToMultiByte(CP_UTF8, 0, tmppath, len, NULL, 0, NULL, NULL);
|
|
char *tmpdir = xmalloc(blen + 1);
|
|
WideCharToMultiByte(CP_UTF8, 0, tmppath, len, tmpdir, blen, NULL, NULL);
|
|
tmpdir[blen] = '\0';
|
|
return tmpdir;
|
|
}
|
|
#elif defined _CS_DARWIN_USER_TEMP_DIR
|
|
char path[MAXPATHLEN];
|
|
size_t len = confstr(_CS_DARWIN_USER_TEMP_DIR, path, sizeof(path));
|
|
if (len > 0) {
|
|
char *tmpdir = xmalloc(len);
|
|
if (len > sizeof(path)) {
|
|
confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdir, len);
|
|
}
|
|
else {
|
|
memcpy(tmpdir, path, len);
|
|
}
|
|
return tmpdir;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
check_tmpdir(const char *dir)
|
|
{
|
|
struct stat st;
|
|
|
|
if (!dir) return FALSE;
|
|
if (stat(dir, &st)) return FALSE;
|
|
#ifndef S_ISDIR
|
|
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
|
#endif
|
|
if (!S_ISDIR(st.st_mode)) return FALSE;
|
|
#ifndef _WIN32
|
|
# ifndef S_IWOTH
|
|
# define S_IWOTH 002
|
|
# endif
|
|
if (st.st_mode & S_IWOTH) {
|
|
# ifdef S_ISVTX
|
|
if (!(st.st_mode & S_ISVTX)) return FALSE;
|
|
# else
|
|
return FALSE;
|
|
# endif
|
|
}
|
|
if (access(dir, W_OK)) return FALSE;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
static char *
|
|
system_tmpdir(void)
|
|
{
|
|
char *tmpdir;
|
|
# define RETURN_ENV(name) \
|
|
if (check_tmpdir(tmpdir = getenv(name))) return ruby_strdup(tmpdir)
|
|
RETURN_ENV("TMPDIR");
|
|
RETURN_ENV("TMP");
|
|
tmpdir = system_default_tmpdir();
|
|
if (check_tmpdir(tmpdir)) return tmpdir;
|
|
return ruby_strdup("/tmp");
|
|
# undef RETURN_ENV
|
|
}
|
|
|
|
/* end of copy */
|
|
|
|
static int
|
|
sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, const char *basename)
|
|
{
|
|
if (tmp_dir_has_dirsep) {
|
|
return snprintf(str, size, "%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, prefix, getpid(), box_id, basename);
|
|
}
|
|
return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename);
|
|
}
|
|
|
|
enum copy_error_type {
|
|
COPY_ERROR_NONE,
|
|
COPY_ERROR_SRC_OPEN,
|
|
COPY_ERROR_DST_OPEN,
|
|
COPY_ERROR_SRC_READ,
|
|
COPY_ERROR_DST_WRITE,
|
|
COPY_ERROR_SRC_STAT,
|
|
COPY_ERROR_DST_CHMOD,
|
|
COPY_ERROR_SYSERR
|
|
};
|
|
|
|
static const char *
|
|
copy_ext_file_error(char *message, size_t size, int copy_retvalue)
|
|
{
|
|
#ifdef _WIN32
|
|
int error = GetLastError();
|
|
char *p = message;
|
|
size_t len = snprintf(message, size, "%d: ", error);
|
|
|
|
#define format_message(sublang) FormatMessage(\
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, \
|
|
NULL, error, MAKELANGID(LANG_NEUTRAL, (sublang)), \
|
|
message + len, size - len, NULL)
|
|
if (format_message(SUBLANG_ENGLISH_US) == 0)
|
|
format_message(SUBLANG_DEFAULT);
|
|
for (p = message + len; *p; p++) {
|
|
if (*p == '\n' || *p == '\r')
|
|
*p = ' ';
|
|
}
|
|
#else
|
|
switch (copy_retvalue) {
|
|
case COPY_ERROR_SRC_OPEN:
|
|
strlcpy(message, "can't open the extension path", size);
|
|
break;
|
|
case COPY_ERROR_DST_OPEN:
|
|
strlcpy(message, "can't open the file to write", size);
|
|
break;
|
|
case COPY_ERROR_SRC_READ:
|
|
strlcpy(message, "failed to read the extension path", size);
|
|
break;
|
|
case COPY_ERROR_DST_WRITE:
|
|
strlcpy(message, "failed to write the extension path", size);
|
|
break;
|
|
case COPY_ERROR_SRC_STAT:
|
|
strlcpy(message, "failed to stat the extension path to copy permissions", size);
|
|
break;
|
|
case COPY_ERROR_DST_CHMOD:
|
|
strlcpy(message, "failed to set permissions to the copied extension path", size);
|
|
break;
|
|
case COPY_ERROR_SYSERR:
|
|
strlcpy(message, strerror(errno), size);
|
|
break;
|
|
case COPY_ERROR_NONE: /* shouldn't be called */
|
|
default:
|
|
rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue);
|
|
}
|
|
#endif
|
|
return message;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
static enum copy_error_type
|
|
copy_stream(int src_fd, int dst_fd)
|
|
{
|
|
char buffer[1024];
|
|
ssize_t rsize;
|
|
|
|
while ((rsize = read(src_fd, buffer, sizeof(buffer))) != 0) {
|
|
if (rsize < 0) return COPY_ERROR_SRC_READ;
|
|
for (size_t written = 0; written < (size_t)rsize;) {
|
|
ssize_t wsize = write(dst_fd, buffer+written, rsize-written);
|
|
if (wsize < 0) return COPY_ERROR_DST_WRITE;
|
|
written += (size_t)wsize;
|
|
}
|
|
}
|
|
return COPY_ERROR_NONE;
|
|
}
|
|
#endif
|
|
|
|
static enum copy_error_type
|
|
copy_ext_file(const char *src_path, const char *dst_path)
|
|
{
|
|
#if defined(_WIN32)
|
|
WCHAR *w_src = rb_w32_mbstr_to_wstr(CP_UTF8, src_path, -1, NULL);
|
|
WCHAR *w_dst = rb_w32_mbstr_to_wstr(CP_UTF8, dst_path, -1, NULL);
|
|
if (!w_src || !w_dst) {
|
|
free(w_src);
|
|
free(w_dst);
|
|
rb_memerror();
|
|
}
|
|
|
|
enum copy_error_type rvalue = CopyFileW(w_src, w_dst, TRUE) ?
|
|
COPY_ERROR_NONE : COPY_ERROR_SYSERR;
|
|
free(w_src);
|
|
free(w_dst);
|
|
return rvalue;
|
|
#else
|
|
# ifdef O_BINARY
|
|
const int bin = O_BINARY;
|
|
# else
|
|
const int bin = 0;
|
|
# endif
|
|
# ifdef O_CLOEXEC
|
|
const int cloexec = O_CLOEXEC;
|
|
# else
|
|
const int cloexec = 0;
|
|
# endif
|
|
const int src_fd = open(src_path, O_RDONLY|cloexec|bin);
|
|
if (src_fd < 0) return COPY_ERROR_SRC_OPEN;
|
|
if (!cloexec) rb_maygvl_fd_fix_cloexec(src_fd);
|
|
|
|
struct stat src_st;
|
|
if (fstat(src_fd, &src_st)) {
|
|
close(src_fd);
|
|
return COPY_ERROR_SRC_STAT;
|
|
}
|
|
|
|
const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|cloexec|bin, S_IRWXU);
|
|
if (dst_fd < 0) {
|
|
close(src_fd);
|
|
return COPY_ERROR_DST_OPEN;
|
|
}
|
|
if (!cloexec) rb_maygvl_fd_fix_cloexec(dst_fd);
|
|
|
|
enum copy_error_type ret = COPY_ERROR_NONE;
|
|
|
|
if (fchmod(dst_fd, src_st.st_mode & 0777)) {
|
|
ret = COPY_ERROR_DST_CHMOD;
|
|
goto done;
|
|
}
|
|
|
|
const size_t count_max = (SIZE_MAX >> 1) + 1;
|
|
(void)count_max;
|
|
|
|
# ifdef HAVE_COPY_FILE_RANGE
|
|
for (;;) {
|
|
ssize_t written = copy_file_range(src_fd, NULL, dst_fd, NULL, count_max, 0);
|
|
if (written == 0) goto done;
|
|
if (written < 0) break;
|
|
}
|
|
# endif
|
|
# ifdef HAVE_FCOPYFILE
|
|
if (fcopyfile(src_fd, dst_fd, NULL, COPYFILE_DATA) == 0) {
|
|
goto done;
|
|
}
|
|
# endif
|
|
# ifdef USE_SENDFILE
|
|
for (;;) {
|
|
ssize_t written = sendfile(src_fd, dst_fd, NULL count_max);
|
|
if (written == 0) goto done;
|
|
if (written < 0) break;
|
|
}
|
|
# endif
|
|
ret = copy_stream(src_fd, dst_fd);
|
|
|
|
done:
|
|
close(src_fd);
|
|
if (dst_fd >= 0) close(dst_fd);
|
|
if (ret != COPY_ERROR_NONE) unlink(dst_path);
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
#if defined __CYGWIN__ || defined DOSISH
|
|
#define isdirsep(x) ((x) == '/' || (x) == '\\')
|
|
#else
|
|
#define isdirsep(x) ((x) == '/')
|
|
#endif
|
|
|
|
#define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0)
|
|
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
|
|
|
|
static void
|
|
fname_without_suffix(const char *fname, char *rvalue, size_t rsize)
|
|
{
|
|
size_t len = strlen(fname);
|
|
const char *pos;
|
|
for (pos = fname + len; pos > fname; pos--) {
|
|
if (IS_SOEXT(pos) || IS_DLEXT(pos)) {
|
|
len = pos - fname;
|
|
break;
|
|
}
|
|
if (fname + len - pos > DLEXT_MAXLEN) break;
|
|
}
|
|
if (len > rsize - 1) len = rsize - 1;
|
|
memcpy(rvalue, fname, len);
|
|
rvalue[len] = '\0';
|
|
}
|
|
|
|
static void
|
|
escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize)
|
|
{
|
|
char *pos;
|
|
const char *leaf = path, *found;
|
|
// `leaf + 1` looks uncomfortable (when leaf == path), but fname must not be the top-dir itself
|
|
while ((found = strstr(leaf + 1, fname)) != NULL) {
|
|
leaf = found; // find the last occurrence for the path like /etc/my-crazy-lib-dir/etc.so
|
|
}
|
|
strlcpy(rvalue, leaf, rsize);
|
|
for (pos = rvalue; *pos; pos++) {
|
|
if (isdirsep(*pos)) {
|
|
*pos = '+';
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
box_ext_cleanup_mark(void *p)
|
|
{
|
|
rb_gc_mark((VALUE)p);
|
|
}
|
|
|
|
static void
|
|
box_ext_cleanup_free(void *p)
|
|
{
|
|
VALUE path = (VALUE)p;
|
|
unlink(RSTRING_PTR(path));
|
|
}
|
|
|
|
static const rb_data_type_t box_ext_cleanup_type = {
|
|
"box_ext_cleanup",
|
|
{box_ext_cleanup_mark, box_ext_cleanup_free},
|
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
};
|
|
|
|
void
|
|
rb_box_cleanup_local_extension(VALUE cleanup)
|
|
{
|
|
void *p = DATA_PTR(cleanup);
|
|
DATA_PTR(cleanup) = NULL;
|
|
#ifndef _WIN32
|
|
if (p) box_ext_cleanup_free(p);
|
|
#endif
|
|
(void)p;
|
|
}
|
|
|
|
static int
|
|
cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg)
|
|
{
|
|
#if defined(_WIN32)
|
|
HMODULE h = (HMODULE)NUM2PTR(value);
|
|
WCHAR module_path[MAXPATHLEN];
|
|
DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path));
|
|
|
|
FreeLibrary(h);
|
|
if (len > 0 && len < numberof(module_path)) DeleteFileW(module_path);
|
|
#endif
|
|
return ST_DELETE;
|
|
}
|
|
|
|
static void
|
|
cleanup_all_local_extensions(VALUE libmap)
|
|
{
|
|
rb_hash_foreach(libmap, cleanup_local_extension_i, 0);
|
|
}
|
|
|
|
VALUE
|
|
rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path, VALUE *cleanup)
|
|
{
|
|
char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN];
|
|
int wrote;
|
|
const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname);
|
|
rb_box_t *box = rb_get_box_t(box_value);
|
|
|
|
fname_without_suffix(fname_ptr, fname2, sizeof(fname2));
|
|
escaped_basename(src_path, fname2, basename, sizeof(basename));
|
|
|
|
wrote = sprint_ext_filename(ext_path, sizeof(ext_path), box->box_id, BOX_TMP_PREFIX, basename);
|
|
if (wrote >= (int)sizeof(ext_path)) {
|
|
rb_bug("Extension file path in the box was too long");
|
|
}
|
|
VALUE new_path = rb_str_new_cstr(ext_path);
|
|
*cleanup = TypedData_Wrap_Struct(0, &box_ext_cleanup_type, NULL);
|
|
enum copy_error_type copy_error = copy_ext_file(src_path, ext_path);
|
|
if (copy_error) {
|
|
char message[1024];
|
|
copy_ext_file_error(message, sizeof(message), copy_error);
|
|
rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %"PRIsVALUE"): %s", ext_path, path, message);
|
|
}
|
|
DATA_PTR(*cleanup) = (void *)new_path;
|
|
return new_path;
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_load(int argc, VALUE *argv, VALUE box)
|
|
{
|
|
VALUE fname, wrap;
|
|
rb_scan_args(argc, argv, "11", &fname, &wrap);
|
|
|
|
rb_vm_frame_flag_set_box_require(GET_EC());
|
|
|
|
VALUE args = rb_ary_new_from_args(2, fname, wrap);
|
|
return rb_load_entrypoint(args);
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_require(VALUE box, VALUE fname)
|
|
{
|
|
rb_vm_frame_flag_set_box_require(GET_EC());
|
|
|
|
return rb_require_string(fname);
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_require_relative(VALUE box, VALUE fname)
|
|
{
|
|
rb_vm_frame_flag_set_box_require(GET_EC());
|
|
|
|
return rb_require_relative_entrypoint(fname);
|
|
}
|
|
|
|
static void
|
|
initialize_master_box(void)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
rb_box_t *master = (rb_box_t *)rb_master_box();
|
|
|
|
master->load_path = rb_ary_new();
|
|
master->expanded_load_path = rb_ary_hidden_new(0);
|
|
master->load_path_snapshot = rb_ary_hidden_new(0);
|
|
master->load_path_check_cache = 0;
|
|
rb_define_singleton_method(master->load_path, "resolve_feature_path", rb_resolve_feature_path, 1);
|
|
|
|
master->loaded_features = rb_ary_new();
|
|
master->loaded_features_snapshot = rb_ary_hidden_new(0);
|
|
master->loaded_features_index = st_init_numtable();
|
|
master->loaded_features_realpaths = rb_hash_new();
|
|
rb_obj_hide(master->loaded_features_realpaths);
|
|
master->loaded_features_realpath_map = rb_hash_new();
|
|
rb_obj_hide(master->loaded_features_realpath_map);
|
|
|
|
master->ruby_dln_libmap = rb_hash_new_with_size(0);
|
|
master->gvar_tbl = rb_hash_new_with_size(0);
|
|
master->classext_cow_classes = NULL; // classext CoW never happen on the master box
|
|
|
|
vm->master_box = master;
|
|
|
|
if (rb_box_available()) {
|
|
VALUE master_box, entry;
|
|
ID id_box_entry;
|
|
CONST_ID(id_box_entry, "__box_entry__");
|
|
|
|
master_box = rb_obj_alloc(rb_cBox);
|
|
RCLASS_SET_PRIME_CLASSEXT_WRITABLE(master_box, true);
|
|
RCLASS_SET_CONST_TBL(master_box, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true);
|
|
|
|
master->box_id = box_generate_id();
|
|
master->box_object = master_box;
|
|
|
|
entry = TypedData_Wrap_Struct(rb_cBoxEntry, &rb_master_box_data_type, master);
|
|
rb_ivar_set(master_box, id_box_entry, entry);
|
|
|
|
rb_gc_register_mark_object(master_box);
|
|
rb_gc_register_mark_object(entry);
|
|
}
|
|
else {
|
|
master->box_id = 1;
|
|
master->box_object = Qnil;
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_eval(VALUE box_value, VALUE str)
|
|
{
|
|
const rb_iseq_t *iseq;
|
|
const rb_box_t *box;
|
|
|
|
StringValue(str);
|
|
|
|
iseq = rb_iseq_compile_iseq(str, rb_str_new_cstr("eval"));
|
|
VM_ASSERT(iseq);
|
|
|
|
box = (const rb_box_t *)rb_get_box_t(box_value);
|
|
|
|
return rb_iseq_eval(iseq, box);
|
|
}
|
|
|
|
static int box_experimental_warned = 0;
|
|
|
|
RUBY_EXTERN const char ruby_api_version_name[];
|
|
|
|
static VALUE
|
|
box_value_initialize(bool root, bool user, bool optional)
|
|
{
|
|
rb_box_t *box;
|
|
VALUE box_value = rb_class_new_instance(0, NULL, rb_cBox);
|
|
|
|
VM_ASSERT(BOX_OBJ_P(box_value));
|
|
|
|
box = rb_get_box_t(box_value);
|
|
box->box_object = box_value;
|
|
box->is_root = root;
|
|
box->is_user = user;
|
|
box->is_optional = optional;
|
|
return box_value;
|
|
}
|
|
|
|
void
|
|
rb_initialize_mandatory_boxes(void)
|
|
{
|
|
VALUE root_box_value, main_box_value;
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
VM_ASSERT(rb_box_available());
|
|
|
|
if (!box_experimental_warned) {
|
|
rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL,
|
|
"Ruby::Box is experimental, and the behavior may change in the future!\n"
|
|
"See https://docs.ruby-lang.org/en/%s/Ruby/Box.html for known issues, etc.",
|
|
ruby_api_version_name);
|
|
box_experimental_warned = 1;
|
|
}
|
|
|
|
root_box_value = box_value_initialize(true, false, false);
|
|
main_box_value = box_value_initialize(false, true, false);
|
|
|
|
rb_const_set(rb_cBox, rb_intern("ROOT"), root_box_value);
|
|
rb_const_set(rb_cBox, rb_intern("MAIN"), main_box_value);
|
|
|
|
vm->root_box = root_box = rb_get_box_t(root_box_value);
|
|
vm->main_box = main_box = rb_get_box_t(main_box_value);
|
|
|
|
// create the writable classext of ::Object explicitly to finalize the set of visible top-level constants
|
|
RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, root_box);
|
|
RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, main_box);
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_inspect(VALUE obj)
|
|
{
|
|
rb_box_t *box;
|
|
VALUE r;
|
|
if (obj == Qfalse) {
|
|
r = rb_str_new_cstr("#<Ruby::Box:master>");
|
|
return r;
|
|
}
|
|
box = rb_get_box_t(obj);
|
|
r = rb_str_new_cstr("#<Ruby::Box:");
|
|
rb_str_concat(r, rb_funcall(LONG2NUM(box->box_id), rb_intern("to_s"), 0));
|
|
if (BOX_MASTER_P(box)) {
|
|
rb_str_cat_cstr(r, ",master");
|
|
}
|
|
if (BOX_ROOT_P(box)) {
|
|
rb_str_cat_cstr(r, ",root");
|
|
}
|
|
if (BOX_USER_P(box)) {
|
|
rb_str_cat_cstr(r, ",user");
|
|
}
|
|
if (BOX_MAIN_P(box)) {
|
|
rb_str_cat_cstr(r, ",main");
|
|
}
|
|
else if (BOX_OPTIONAL_P(box)) {
|
|
rb_str_cat_cstr(r, ",optional");
|
|
}
|
|
rb_str_cat_cstr(r, ">");
|
|
return r;
|
|
}
|
|
|
|
static VALUE
|
|
rb_box_loading_func(int argc, VALUE *argv, VALUE _self)
|
|
{
|
|
rb_vm_frame_flag_set_box_require(GET_EC());
|
|
return rb_call_super(argc, argv);
|
|
}
|
|
|
|
static void
|
|
box_define_loader_method(const char *name)
|
|
{
|
|
rb_define_private_method(rb_mBoxLoader, name, rb_box_loading_func, -1);
|
|
rb_define_singleton_method(rb_mBoxLoader, name, rb_box_loading_func, -1);
|
|
}
|
|
|
|
void
|
|
Init_master_box(void)
|
|
{
|
|
master_box->loading_table = st_init_strtable();
|
|
}
|
|
|
|
void
|
|
Init_enable_box(void)
|
|
{
|
|
const char *env = getenv("RUBY_BOX");
|
|
if (env && strlen(env) == 1 && env[0] == '1') {
|
|
ruby_box_enabled = true;
|
|
}
|
|
else {
|
|
ruby_box_init_done = true;
|
|
}
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_s_master(VALUE recv)
|
|
{
|
|
return master_box->box_object;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_s_root(VALUE recv)
|
|
{
|
|
return root_box->box_object;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_s_main(VALUE recv)
|
|
{
|
|
return main_box->box_object;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_master_p(VALUE box_value)
|
|
{
|
|
const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
|
|
return RBOOL(BOX_MASTER_P(box));
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_root_p(VALUE box_value)
|
|
{
|
|
const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
|
|
return RBOOL(BOX_ROOT_P(box));
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_box_main_p(VALUE box_value)
|
|
{
|
|
const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value);
|
|
return RBOOL(BOX_MAIN_P(box));
|
|
}
|
|
|
|
#if RUBY_DEBUG
|
|
|
|
static const char *
|
|
classname(VALUE klass)
|
|
{
|
|
VALUE p;
|
|
if (!klass) {
|
|
return "Qfalse";
|
|
}
|
|
p = RCLASSEXT_CLASSPATH(RCLASS_EXT_PRIME(klass));
|
|
if (RTEST(p))
|
|
return RSTRING_PTR(p);
|
|
if (RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS))
|
|
return "AnyClassValue";
|
|
return "NonClassValue";
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
dump_classext_methods_i(ID mid, VALUE _val, void *data)
|
|
{
|
|
VALUE ary = (VALUE)data;
|
|
rb_ary_push(ary, rb_id2str(mid));
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
dump_classext_constants_i(ID mid, VALUE _val, void *data)
|
|
{
|
|
VALUE ary = (VALUE)data;
|
|
rb_ary_push(ary, rb_id2str(mid));
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
dump_classext_i(rb_classext_t *ext, bool is_prime, VALUE _recv, void *data)
|
|
{
|
|
char buf[4096];
|
|
struct rb_id_table *tbl;
|
|
VALUE ary, res = (VALUE)data;
|
|
|
|
snprintf(buf, 4096, "Ruby::Box %ld:%s classext %p\n",
|
|
RCLASSEXT_BOX(ext)->box_id, is_prime ? " prime" : "", (void *)ext);
|
|
rb_str_cat_cstr(res, buf);
|
|
|
|
snprintf(buf, 2048, " Super: %s\n", classname(RCLASSEXT_SUPER(ext)));
|
|
rb_str_cat_cstr(res, buf);
|
|
|
|
tbl = RCLASSEXT_M_TBL(ext);
|
|
if (tbl) {
|
|
ary = rb_ary_new_capa((long)rb_id_table_size(tbl));
|
|
rb_id_table_foreach(RCLASSEXT_M_TBL(ext), dump_classext_methods_i, (void *)ary);
|
|
rb_ary_sort_bang(ary);
|
|
snprintf(buf, 4096, " Methods(%ld): ", RARRAY_LEN(ary));
|
|
rb_str_cat_cstr(res, buf);
|
|
rb_str_concat(res, rb_ary_join(ary, rb_str_new_cstr(",")));
|
|
rb_str_cat_cstr(res, "\n");
|
|
}
|
|
else {
|
|
rb_str_cat_cstr(res, " Methods(0): .\n");
|
|
}
|
|
|
|
tbl = RCLASSEXT_CONST_TBL(ext);
|
|
if (tbl) {
|
|
ary = rb_ary_new_capa((long)rb_id_table_size(tbl));
|
|
rb_id_table_foreach(tbl, dump_classext_constants_i, (void *)ary);
|
|
rb_ary_sort_bang(ary);
|
|
snprintf(buf, 4096, " Constants(%ld): ", RARRAY_LEN(ary));
|
|
rb_str_cat_cstr(res, buf);
|
|
rb_str_concat(res, rb_ary_join(ary, rb_str_new_cstr(",")));
|
|
rb_str_cat_cstr(res, "\n");
|
|
}
|
|
else {
|
|
rb_str_cat_cstr(res, " Constants(0): .\n");
|
|
}
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
rb_f_dump_classext(VALUE recv, VALUE klass)
|
|
{
|
|
/*
|
|
* The desired output String value is:
|
|
* Class: 0x88800932 (String) [singleton]
|
|
* Prime classext box(2,main), readable(t), writable(f)
|
|
* Non-prime classexts: 3
|
|
* Box 2: prime classext 0x88800933
|
|
* Super: Object
|
|
* Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ...
|
|
* Constants(12): FOO, Bar, ...
|
|
* Box 5: classext 0x88800934
|
|
* Super: Object
|
|
* Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ...
|
|
* Constants(12): FOO, Bar, ...
|
|
*/
|
|
char buf[2048];
|
|
VALUE res;
|
|
const rb_classext_t *ext;
|
|
const rb_box_t *box;
|
|
st_table *classext_tbl;
|
|
|
|
if (!(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE))) {
|
|
snprintf(buf, 2048, "Non-class/module value: %p (%s)\n", (void *)klass, rb_type_str(BUILTIN_TYPE(klass)));
|
|
return rb_str_new_cstr(buf);
|
|
}
|
|
|
|
if (RB_TYPE_P(klass, T_CLASS)) {
|
|
snprintf(buf, 2048, "Class: %p (%s)%s\n",
|
|
(void *)klass, classname(klass), RCLASS_SINGLETON_P(klass) ? " [singleton]" : "");
|
|
}
|
|
else {
|
|
snprintf(buf, 2048, "Module: %p (%s)\n", (void *)klass, classname(klass));
|
|
}
|
|
res = rb_str_new_cstr(buf);
|
|
|
|
ext = RCLASS_EXT_PRIME(klass);
|
|
box = RCLASSEXT_BOX(ext);
|
|
snprintf(buf, 2048, "Prime classext box(%ld,%s), readable(%s), writable(%s)\n",
|
|
box->box_id,
|
|
BOX_MASTER_P(box) ? "master" : (BOX_ROOT_P(box) ? "root" : (BOX_MAIN_P(box) ? "main" : "optional")),
|
|
RCLASS_PRIME_CLASSEXT_READABLE_P(klass) ? "t" : "f",
|
|
RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass) ? "t" : "f");
|
|
rb_str_cat_cstr(res, buf);
|
|
|
|
classext_tbl = RCLASS_CLASSEXT_TBL(klass);
|
|
if (!classext_tbl) {
|
|
rb_str_cat_cstr(res, "Non-prime classexts: 0\n");
|
|
}
|
|
else {
|
|
snprintf(buf, 2048, "Non-prime classexts: %zu\n", st_table_size(classext_tbl));
|
|
rb_str_cat_cstr(res, buf);
|
|
}
|
|
|
|
rb_class_classext_foreach(klass, dump_classext_i, (void *)res);
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif /* RUBY_DEBUG */
|
|
|
|
/*
|
|
* Document-class: Ruby::Box
|
|
*
|
|
* :markup: markdown
|
|
* :include: doc/language/box.md
|
|
*/
|
|
void
|
|
Init_Box(void)
|
|
{
|
|
tmp_dir = system_tmpdir();
|
|
tmp_dir_has_dirsep = (strcmp(tmp_dir + (strlen(tmp_dir) - strlen(DIRSEP)), DIRSEP) == 0);
|
|
|
|
VALUE mRuby = rb_define_module("Ruby");
|
|
|
|
rb_cBox = rb_define_class_under(mRuby, "Box", rb_cModule);
|
|
rb_define_method(rb_cBox, "initialize", box_initialize, 0);
|
|
|
|
/* :nodoc: */
|
|
rb_cBoxEntry = rb_define_class_under(rb_cBox, "Entry", rb_cObject);
|
|
rb_define_alloc_func(rb_cBoxEntry, rb_box_entry_alloc);
|
|
|
|
initialize_master_box();
|
|
|
|
/* :nodoc: */
|
|
rb_mBoxLoader = rb_define_module_under(rb_cBox, "Loader");
|
|
box_define_loader_method("require");
|
|
box_define_loader_method("require_relative");
|
|
box_define_loader_method("load");
|
|
|
|
if (rb_box_available()) {
|
|
rb_include_module(rb_cObject, rb_mBoxLoader);
|
|
|
|
rb_define_singleton_method(rb_cBox, "master", rb_box_s_master, 0);
|
|
rb_define_singleton_method(rb_cBox, "root", rb_box_s_root, 0);
|
|
rb_define_singleton_method(rb_cBox, "main", rb_box_s_main, 0);
|
|
rb_define_method(rb_cBox, "master?", rb_box_master_p, 0);
|
|
rb_define_method(rb_cBox, "root?", rb_box_root_p, 0);
|
|
rb_define_method(rb_cBox, "main?", rb_box_main_p, 0);
|
|
|
|
#if RUBY_DEBUG
|
|
rb_define_global_function("dump_classext", rb_f_dump_classext, 1);
|
|
#endif
|
|
}
|
|
|
|
rb_define_singleton_method(rb_cBox, "enabled?", rb_box_s_getenabled, 0);
|
|
rb_define_singleton_method(rb_cBox, "current", rb_box_s_current, 0);
|
|
|
|
rb_define_method(rb_cBox, "load_path", rb_box_load_path, 0);
|
|
rb_define_method(rb_cBox, "load", rb_box_load, -1);
|
|
rb_define_method(rb_cBox, "require", rb_box_require, 1);
|
|
rb_define_method(rb_cBox, "require_relative", rb_box_require_relative, 1);
|
|
rb_define_method(rb_cBox, "eval", rb_box_eval, 1);
|
|
|
|
rb_define_method(rb_cBox, "inspect", rb_box_inspect, 0);
|
|
}
|