Files
ruby/box.c
T
Satoshi Tagomori 276f0d9b3e [Bug #21881] Separate the master and root box
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.
2026-05-10 09:38:45 +09:00

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);
}