Bug #3924 » require_performance.diff
| .gitignore | ||
|---|---|---|
|
# /win32/
|
||
|
/win32/*.ico
|
||
|
ext/win32ole/.document
|
||
| array.c | ||
|---|---|---|
|
return (VALUE)ary;
|
||
|
}
|
||
|
static VALUE
|
||
|
VALUE // TODO: Removed the static declaration off this, figure out if that is OK
|
||
|
ary_new(VALUE klass, long capa)
|
||
|
{
|
||
|
VALUE ary;
|
||
| enumerator.c | ||
|---|---|---|
|
id_rewind = rb_intern("rewind");
|
||
|
id_each = rb_intern("each");
|
||
|
sym_each = ID2SYM(id_each);
|
||
|
rb_provide("enumerator.so"); /* for backward compatibility */
|
||
|
}
|
||
| file.c | ||
|---|---|---|
|
* Return <code>true</code> if the named file exists.
|
||
|
*/
|
||
|
static VALUE
|
||
|
VALUE // TODO: Mark this as static again
|
||
|
rb_file_exist_p(VALUE obj, VALUE fname)
|
||
|
{
|
||
|
struct stat st;
|
||
| lib/enumerator.rb | ||
|---|---|---|
|
# This class is now defined entirely in enumerator.c and is always available.
|
||
|
# This file needs to remain here for backwards compatibility, so that `require
|
||
|
# "enumerator"` will not raise an exception.
|
||
| load.c | ||
|---|---|---|
|
#endif
|
||
|
VALUE rb_f_require(VALUE, VALUE);
|
||
|
VALUE rb_f_require_relative(VALUE, VALUE);
|
||
|
static VALUE rb_f_load(int, VALUE *);
|
||
|
VALUE rb_require_safe(VALUE, int);
|
||
|
static int rb_file_has_been_required(VALUE);
|
||
|
static int rb_file_is_being_required(VALUE);
|
||
|
static int rb_file_is_ruby(VALUE);
|
||
|
static st_table * get_loaded_features_hash(void);
|
||
|
static void rb_load_internal(VALUE, int);
|
||
|
static char * load_lock(const char *);
|
||
|
static void load_unlock(const char *, int);
|
||
|
static void load_failed(VALUE fname);
|
||
|
void rb_provide(const char *feature);
|
||
|
static void rb_provide_feature(VALUE);
|
||
|
/*
|
||
|
* TODO: These functions are all conceptually related, and should be extracted
|
||
|
* into a separate file.
|
||
|
*/
|
||
|
static VALUE rb_locate_file(VALUE);
|
||
|
static VALUE rb_locate_file_absolute(VALUE);
|
||
|
static VALUE rb_locate_file_relative(VALUE);
|
||
|
static VALUE rb_locate_file_in_load_path(VALUE);
|
||
|
static VALUE rb_locate_file_with_extensions(VALUE);
|
||
|
static int rb_path_is_absolute(VALUE);
|
||
|
static int rb_path_is_relative(VALUE);
|
||
|
VALUE rb_get_expanded_load_path();
|
||
|
/*
|
||
|
* TODO: These functions are all conceptually related, and should be extracted
|
||
|
* into a separate file.
|
||
|
*/
|
||
|
static VALUE rb_cLoadedFeaturesProxy;
|
||
|
static void rb_rehash_loaded_features();
|
||
|
static VALUE rb_loaded_features_hook(int, VALUE*, VALUE);
|
||
|
static void define_loaded_features_proxy();
|
||
|
VALUE ary_new(VALUE, long); // array.c
|
||
|
static const char *const loadable_ext[] = {
|
||
|
".rb", DLEXT,
|
||
|
".rb",
|
||
|
DLEXT,
|
||
|
#ifdef DLEXT2
|
||
|
DLEXT2,
|
||
|
#endif
|
||
| ... | ... | |
|
return load_path;
|
||
|
}
|
||
|
static st_table *
|
||
|
get_loaded_features_hash(void)
|
||
|
{
|
||
|
st_table* loaded_features_hash;
|
||
|
loaded_features_hash = GET_VM()->loaded_features_hash;
|
||
|
if (!loaded_features_hash) {
|
||
|
GET_VM()->loaded_features_hash = loaded_features_hash = st_init_strcasetable();
|
||
|
}
|
||
|
return loaded_features_hash;
|
||
|
}
|
||
|
static st_table *
|
||
|
get_filename_expansion_hash(void)
|
||
|
{
|
||
|
st_table* filename_expansion_hash;
|
||
|
filename_expansion_hash = GET_VM()->filename_expansion_hash;
|
||
|
if (!filename_expansion_hash) {
|
||
|
GET_VM()->filename_expansion_hash = filename_expansion_hash = st_init_strcasetable();
|
||
|
}
|
||
|
return filename_expansion_hash;
|
||
|
}
|
||
|
VALUE
|
||
|
rb_get_expanded_load_path(void)
|
||
|
{
|
||
| ... | ... | |
|
return GET_VM()->loading_table;
|
||
|
}
|
||
|
static VALUE
|
||
|
loaded_feature_path(const char *name, long vlen, const char *feature, long len,
|
||
|
int type, VALUE load_path)
|
||
|
{
|
||
|
long i;
|
||
|
for (i = 0; i < RARRAY_LEN(load_path); ++i) {
|
||
|
VALUE p = RARRAY_PTR(load_path)[i];
|
||
|
const char *s = StringValuePtr(p);
|
||
|
long n = RSTRING_LEN(p);
|
||
|
if (vlen < n + len + 1) continue;
|
||
|
if (n && (strncmp(name, s, n) || name[n] != '/')) continue;
|
||
|
if (strncmp(name + n + 1, feature, len)) continue;
|
||
|
if (name[n+len+1] && name[n+len+1] != '.') continue;
|
||
|
switch (type) {
|
||
|
case 's':
|
||
|
if (IS_DLEXT(&name[n+len+1])) return p;
|
||
|
break;
|
||
|
case 'r':
|
||
|
if (IS_RBEXT(&name[n+len+1])) return p;
|
||
|
break;
|
||
|
default:
|
||
|
return p;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
struct loaded_feature_searching {
|
||
|
const char *name;
|
||
|
long len;
|
||
|
int type;
|
||
|
VALUE load_path;
|
||
|
const char *result;
|
||
|
};
|
||
|
static int
|
||
|
loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f)
|
||
|
{
|
||
|
const char *s = (const char *)v;
|
||
|
struct loaded_feature_searching *fp = (struct loaded_feature_searching *)f;
|
||
|
VALUE p = loaded_feature_path(s, strlen(s), fp->name, fp->len,
|
||
|
fp->type, fp->load_path);
|
||
|
if (!p) return ST_CONTINUE;
|
||
|
fp->result = s;
|
||
|
return ST_STOP;
|
||
|
}
|
||
|
static int
|
||
|
rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn)
|
||
|
{
|
||
|
VALUE v, features, p, load_path = 0;
|
||
|
const char *f, *e;
|
||
|
long i, len, elen, n;
|
||
|
st_table *loading_tbl;
|
||
|
st_data_t data;
|
||
|
int type;
|
||
|
if (fn) *fn = 0;
|
||
|
if (ext) {
|
||
|
elen = strlen(ext);
|
||
|
len = strlen(feature) - elen;
|
||
|
type = rb ? 'r' : 's';
|
||
|
}
|
||
|
else {
|
||
|
len = strlen(feature);
|
||
|
elen = 0;
|
||
|
type = 0;
|
||
|
}
|
||
|
features = get_loaded_features();
|
||
|
for (i = 0; i < RARRAY_LEN(features); ++i) {
|
||
|
v = RARRAY_PTR(features)[i];
|
||
|
f = StringValuePtr(v);
|
||
|
if ((n = RSTRING_LEN(v)) < len) continue;
|
||
|
if (strncmp(f, feature, len) != 0) {
|
||
|
if (expanded) continue;
|
||
|
if (!load_path) load_path = rb_get_expanded_load_path();
|
||
|
if (!(p = loaded_feature_path(f, n, feature, len, type, load_path)))
|
||
|
continue;
|
||
|
expanded = 1;
|
||
|
f += RSTRING_LEN(p) + 1;
|
||
|
}
|
||
|
if (!*(e = f + len)) {
|
||
|
if (ext) continue;
|
||
|
return 'u';
|
||
|
}
|
||
|
if (*e != '.') continue;
|
||
|
if ((!rb || !ext) && (IS_SOEXT(e) || IS_DLEXT(e))) {
|
||
|
return 's';
|
||
|
}
|
||
|
if ((rb || !ext) && (IS_RBEXT(e))) {
|
||
|
return 'r';
|
||
|
}
|
||
|
}
|
||
|
loading_tbl = get_loading_table();
|
||
|
if (loading_tbl) {
|
||
|
f = 0;
|
||
|
if (!expanded) {
|
||
|
struct loaded_feature_searching fs;
|
||
|
fs.name = feature;
|
||
|
fs.len = len;
|
||
|
fs.type = type;
|
||
|
fs.load_path = load_path ? load_path : rb_get_load_path();
|
||
|
fs.result = 0;
|
||
|
st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs);
|
||
|
if ((f = fs.result) != 0) {
|
||
|
if (fn) *fn = f;
|
||
|
goto loading;
|
||
|
}
|
||
|
}
|
||
|
if (st_get_key(loading_tbl, (st_data_t)feature, &data)) {
|
||
|
if (fn) *fn = (const char*)data;
|
||
|
loading:
|
||
|
if (!ext) return 'u';
|
||
|
return !IS_RBEXT(ext) ? 's' : 'r';
|
||
|
}
|
||
|
else {
|
||
|
VALUE bufstr;
|
||
|
char *buf;
|
||
|
if (ext && *ext) return 0;
|
||
|
bufstr = rb_str_tmp_new(len + DLEXT_MAXLEN);
|
||
|
buf = RSTRING_PTR(bufstr);
|
||
|
MEMCPY(buf, feature, char, len);
|
||
|
for (i = 0; (e = loadable_ext[i]) != 0; i++) {
|
||
|
strlcpy(buf + len, e, DLEXT_MAXLEN + 1);
|
||
|
if (st_get_key(loading_tbl, (st_data_t)buf, &data)) {
|
||
|
rb_str_resize(bufstr, 0);
|
||
|
if (fn) *fn = (const char*)data;
|
||
|
return i ? 's' : 'r';
|
||
|
}
|
||
|
}
|
||
|
rb_str_resize(bufstr, 0);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
int
|
||
|
rb_provided(const char *feature)
|
||
|
{
|
||
|
return rb_feature_provided(feature, 0);
|
||
|
}
|
||
|
int
|
||
|
rb_feature_provided(const char *feature, const char **loading)
|
||
|
/* Mark the given feature as loaded, after it has been evaluated. */
|
||
|
static void
|
||
|
rb_provide_feature(VALUE feature)
|
||
|
{
|
||
|
const char *ext = strrchr(feature, '.');
|
||
|
volatile VALUE fullpath = 0;
|
||
|
int frozen = 0;
|
||
|
st_table* loaded_features_hash;
|
||
|
if (*feature == '.' &&
|
||
|
(feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) {
|
||
|
fullpath = rb_file_expand_path(rb_str_new2(feature), Qnil);
|
||
|
feature = RSTRING_PTR(fullpath);
|
||
|
}
|
||
|
if (ext && !strchr(ext, '/')) {
|
||
|
if (IS_RBEXT(ext)) {
|
||
|
if (rb_feature_p(feature, ext, TRUE, FALSE, loading)) return TRUE;
|
||
|
return FALSE;
|
||
|
if (OBJ_FROZEN(get_loaded_features())) {
|
||
|
rb_raise(rb_eRuntimeError,
|
||
|
"$LOADED_FEATURES is frozen; cannot append feature");
|
||
|
}
|
||
|
else if (IS_SOEXT(ext) || IS_DLEXT(ext)) {
|
||
|
if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
if (rb_feature_p(feature, 0, TRUE, FALSE, loading))
|
||
|
return TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
static void
|
||
|
rb_provide_feature(VALUE feature)
|
||
|
{
|
||
|
if (OBJ_FROZEN(get_loaded_features())) {
|
||
|
rb_raise(rb_eRuntimeError,
|
||
|
"$LOADED_FEATURES is frozen; cannot append feature");
|
||
|
}
|
||
|
rb_ary_push(get_loaded_features(), feature);
|
||
|
loaded_features_hash = get_loaded_features_hash();
|
||
|
st_insert(
|
||
|
loaded_features_hash,
|
||
|
(st_data_t)ruby_strdup(RSTRING_PTR(feature)),
|
||
|
(st_data_t)rb_barrier_new());
|
||
|
rb_ary_push(get_loaded_features(), feature);
|
||
|
}
|
||
|
void
|
||
| ... | ... | |
|
/*
|
||
|
* call-seq:
|
||
|
* require(string) -> true or false
|
||
|
*
|
||
|
* Ruby tries to load the library named _string_, returning
|
||
|
* +true+ if successful. If the filename does not resolve to
|
||
|
* an absolute path, it will be searched for in the directories listed
|
||
|
* in <code>$:</code>. If the file has the extension ``.rb'', it is
|
||
|
* loaded as a source file; if the extension is ``.so'', ``.o'', or
|
||
|
* ``.dll'', or whatever the default shared library extension is on
|
||
|
* the current platform, Ruby loads the shared library as a Ruby
|
||
|
* extension. Otherwise, Ruby tries adding ``.rb'', ``.so'', and so on
|
||
|
* to the name. The name of the loaded feature is added to the array in
|
||
|
* <code>$"</code>. A feature will not be loaded if its name already
|
||
|
* appears in <code>$"</code>. The file name is converted to an absolute
|
||
|
* path, so ``<code>require 'a'; require './a'</code>'' will not load
|
||
|
* <code>a.rb</code> twice.
|
||
|
*
|
||
|
* require "my-library.rb"
|
||
|
* require "db-driver"
|
||
|
*/
|
||
|
VALUE
|
||
|
rb_f_require(VALUE obj, VALUE fname)
|
||
|
{
|
||
|
return rb_require_safe(fname, rb_safe_level());
|
||
|
}
|
||
|
/*
|
||
|
* call-seq:
|
||
|
* require_relative(string) -> true or false
|
||
|
*
|
||
| ... | ... | |
|
return rb_require_safe(rb_file_absolute_path(fname, base), rb_safe_level());
|
||
|
}
|
||
|
static int
|
||
|
search_required(VALUE fname, volatile VALUE *path, int safe_level)
|
||
|
{
|
||
|
VALUE tmp;
|
||
|
char *ext, *ftptr;
|
||
|
int type, ft = 0;
|
||
|
const char *loading;
|
||
|
*path = 0;
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(fname), '.');
|
||
|
if (ext && !strchr(ext, '/')) {
|
||
|
if (IS_RBEXT(ext)) {
|
||
|
if (rb_feature_p(ftptr, ext, TRUE, FALSE, &loading)) {
|
||
|
if (loading) *path = rb_str_new2(loading);
|
||
|
return 'r';
|
||
|
}
|
||
|
if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) {
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
|
||
|
if (!rb_feature_p(ftptr, ext, TRUE, TRUE, &loading) || loading)
|
||
|
*path = tmp;
|
||
|
return 'r';
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
else if (IS_SOEXT(ext)) {
|
||
|
if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) {
|
||
|
if (loading) *path = rb_str_new2(loading);
|
||
|
return 's';
|
||
|
}
|
||
|
tmp = rb_str_new(RSTRING_PTR(fname), ext - RSTRING_PTR(fname));
|
||
|
#ifdef DLEXT2
|
||
|
OBJ_FREEZE(tmp);
|
||
|
if (rb_find_file_ext_safe(&tmp, loadable_ext + 1, safe_level)) {
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
|
||
|
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
|
||
|
*path = tmp;
|
||
|
return 's';
|
||
|
}
|
||
|
#else
|
||
|
rb_str_cat2(tmp, DLEXT);
|
||
|
OBJ_FREEZE(tmp);
|
||
|
if ((tmp = rb_find_file_safe(tmp, safe_level)) != 0) {
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
|
||
|
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
|
||
|
*path = tmp;
|
||
|
return 's';
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
else if (IS_DLEXT(ext)) {
|
||
|
if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) {
|
||
|
if (loading) *path = rb_str_new2(loading);
|
||
|
return 's';
|
||
|
}
|
||
|
if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) {
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
|
||
|
if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
|
||
|
*path = tmp;
|
||
|
return 's';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ((ft = rb_feature_p(ftptr, 0, FALSE, FALSE, &loading)) == 'r') {
|
||
|
if (loading) *path = rb_str_new2(loading);
|
||
|
return 'r';
|
||
|
}
|
||
|
tmp = fname;
|
||
|
type = rb_find_file_ext_safe(&tmp, loadable_ext, safe_level);
|
||
|
switch (type) {
|
||
|
case 0:
|
||
|
if (ft)
|
||
|
break;
|
||
|
ftptr = RSTRING_PTR(tmp);
|
||
|
return rb_feature_p(ftptr, 0, FALSE, TRUE, 0);
|
||
|
default:
|
||
|
if (ft)
|
||
|
break;
|
||
|
case 1:
|
||
|
ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
|
||
|
if (rb_feature_p(ftptr, ext, !--type, TRUE, &loading) && !loading)
|
||
|
break;
|
||
|
*path = tmp;
|
||
|
}
|
||
|
return type ? 's' : 'r';
|
||
|
}
|
||
|
static void
|
||
|
load_failed(VALUE fname)
|
||
| ... | ... | |
|
}
|
||
|
VALUE
|
||
|
rb_require_safe(VALUE fname, int safe)
|
||
|
{
|
||
|
volatile VALUE result = Qnil;
|
||
|
rb_thread_t *th = GET_THREAD();
|
||
|
volatile VALUE errinfo = th->errinfo;
|
||
|
int state;
|
||
|
struct {
|
||
|
int safe;
|
||
|
} volatile saved;
|
||
|
char *volatile ftptr = 0;
|
||
|
PUSH_TAG();
|
||
|
saved.safe = rb_safe_level();
|
||
|
if ((state = EXEC_TAG()) == 0) {
|
||
|
VALUE path;
|
||
|
long handle;
|
||
|
int found;
|
||
|
rb_set_safe_level_force(safe);
|
||
|
FilePathValue(fname);
|
||
|
rb_set_safe_level_force(0);
|
||
|
found = search_required(fname, &path, safe);
|
||
|
if (found) {
|
||
|
if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) {
|
||
|
result = Qfalse;
|
||
|
}
|
||
|
else {
|
||
|
switch (found) {
|
||
|
case 'r':
|
||
|
rb_load_internal(path, 0);
|
||
|
break;
|
||
|
case 's':
|
||
|
handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
|
||
|
path, 0, path);
|
||
|
rb_ary_push(ruby_dln_librefs, LONG2NUM(handle));
|
||
|
break;
|
||
|
}
|
||
|
rb_provide_feature(path);
|
||
|
result = Qtrue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
POP_TAG();
|
||
|
load_unlock(ftptr, !state);
|
||
|
rb_set_safe_level_force(saved.safe);
|
||
|
if (state) {
|
||
|
JUMP_TAG(state);
|
||
|
}
|
||
|
if (NIL_P(result)) {
|
||
|
load_failed(fname);
|
||
|
}
|
||
|
th->errinfo = errinfo;
|
||
|
return result;
|
||
|
}
|
||
|
VALUE
|
||
|
rb_require(const char *fname)
|
||
|
{
|
||
|
VALUE fn = rb_str_new2(fname);
|
||
| ... | ... | |
|
return rb_mod_autoload_p(klass, sym);
|
||
|
}
|
||
|
VALUE
|
||
|
rb_file_exist_p(VALUE obj, VALUE path);
|
||
|
static int
|
||
|
rb_feature_exists(VALUE expanded_path)
|
||
|
{
|
||
|
return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue;
|
||
|
}
|
||
|
const char *available_extensions[] = {
|
||
|
".rb",
|
||
|
DLEXT,
|
||
|
#ifdef DLEXT2
|
||
|
DLEXT2,
|
||
|
#endif
|
||
|
""
|
||
|
};
|
||
|
#ifdef DLEXT2
|
||
|
VALUE available_ext_rb_str[4];
|
||
|
#else
|
||
|
VALUE available_ext_rb_str[3];
|
||
|
#endif
|
||
|
const char *alternate_dl_extensions[] = {
|
||
|
DLEXT,
|
||
|
#ifdef DLEXT2
|
||
|
DLEXT2
|
||
|
#endif
|
||
|
};
|
||
|
#define CHAR_ARRAY_LEN(array) sizeof(array) / sizeof(char*)
|
||
|
#define VALUE_ARRAY_LEN(array) sizeof(array) / sizeof(VALUE)
|
||
|
static VALUE
|
||
|
rb_locate_file_with_extensions(VALUE base_file_name) {
|
||
|
unsigned int j;
|
||
|
VALUE file_name_with_extension;
|
||
|
VALUE extension;
|
||
|
VALUE directory, basename;
|
||
|
extension = rb_funcall(rb_cFile, rb_intern("extname"), 1, base_file_name);
|
||
|
if (RSTRING_LEN(extension) == 0) {
|
||
|
for (j = 0; j < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) {
|
||
|
file_name_with_extension = rb_str_plus(
|
||
|
base_file_name,
|
||
|
available_ext_rb_str[j]);
|
||
|
if (rb_feature_exists(file_name_with_extension)) {
|
||
|
return file_name_with_extension;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (rb_feature_exists(base_file_name)) {
|
||
|
return base_file_name;
|
||
|
} else {
|
||
|
for (j = 0; j < CHAR_ARRAY_LEN(alternate_dl_extensions); ++j) {
|
||
|
// Try loading the native DLEXT version of this platform.
|
||
|
// This allows 'pathname.so' to require 'pathname.bundle' on OSX
|
||
|
directory = rb_file_dirname(base_file_name);
|
||
|
basename = rb_funcall(rb_cFile, rb_intern("basename"), 2,
|
||
|
base_file_name, extension);
|
||
|
basename = rb_str_cat2(basename, alternate_dl_extensions[j]);
|
||
|
file_name_with_extension = rb_funcall(rb_cFile, rb_intern("join"), 2,
|
||
|
directory, basename);
|
||
|
if (rb_feature_exists(file_name_with_extension)) {
|
||
|
return file_name_with_extension;
|
||
|
}
|
||
|
// Also try loading 'dot.dot.bundle' for 'dot.dot'
|
||
|
file_name_with_extension = rb_str_plus(
|
||
|
base_file_name,
|
||
|
rb_str_new2(alternate_dl_extensions[j]));
|
||
|
if (rb_feature_exists(file_name_with_extension)) {
|
||
|
return file_name_with_extension;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Qnil;
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_locate_file_absolute(VALUE fname)
|
||
|
{
|
||
|
return rb_locate_file_with_extensions(fname);
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_locate_file_relative(VALUE fname)
|
||
|
{
|
||
|
return rb_locate_file_with_extensions(rb_file_expand_path(fname, Qnil));
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_locate_file_in_load_path(VALUE path)
|
||
|
{
|
||
|
long i, j;
|
||
|
VALUE load_path = rb_get_expanded_load_path();
|
||
|
VALUE expanded_file_name = Qnil;
|
||
|
VALUE base_file_name = Qnil;
|
||
|
VALUE sep = rb_str_new2("/");
|
||
|
for (i = 0; i < RARRAY_LEN(load_path); ++i) {
|
||
|
VALUE directory = RARRAY_PTR(load_path)[i];
|
||
|
base_file_name = rb_str_plus(directory, sep);
|
||
|
base_file_name = rb_str_concat(base_file_name, path);
|
||
|
expanded_file_name = rb_locate_file_with_extensions(base_file_name);
|
||
|
if (expanded_file_name != Qnil) {
|
||
|
return expanded_file_name;
|
||
|
}
|
||
|
}
|
||
|
return Qnil;
|
||
|
}
|
||
|
static int
|
||
|
rb_path_is_relative(VALUE path)
|
||
|
{
|
||
|
const char * path_ptr = RSTRING_PTR(path);
|
||
|
const char * current_directory = "./";
|
||
|
const char * parent_directory = "../";
|
||
|
return (
|
||
|
strncmp(current_directory, path_ptr, 2) == 0 ||
|
||
|
strncmp(parent_directory, path_ptr, 3) == 0
|
||
|
);
|
||
|
}
|
||
|
static int
|
||
|
rb_file_is_ruby(VALUE path)
|
||
|
{
|
||
|
const char * ext;
|
||
|
ext = ruby_find_extname(RSTRING_PTR(path), 0);
|
||
|
return ext && IS_RBEXT(ext);
|
||
|
}
|
||
|
static int
|
||
|
rb_path_is_absolute(VALUE path)
|
||
|
{
|
||
|
// Delegate to file.c
|
||
|
return rb_is_absolute_path(RSTRING_PTR(path));
|
||
|
}
|
||
|
static int
|
||
|
rb_file_has_been_required(VALUE expanded_path)
|
||
|
{
|
||
|
st_data_t data;
|
||
|
st_data_t path_key = (st_data_t)RSTRING_PTR(expanded_path);
|
||
|
st_table *loaded_features_hash = get_loaded_features_hash();
|
||
|
return st_lookup(loaded_features_hash, path_key, &data);
|
||
|
}
|
||
|
static int
|
||
|
rb_file_is_being_required(VALUE full_path) {
|
||
|
const char *ftptr = RSTRING_PTR(full_path);
|
||
|
st_data_t data;
|
||
|
st_table *loading_tbl = get_loading_table();
|
||
|
return (loading_tbl && st_lookup(loading_tbl, (st_data_t)ftptr, &data));
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_get_cached_expansion(VALUE filename)
|
||
|
{
|
||
|
st_data_t data;
|
||
|
st_data_t path_key = (st_data_t)RSTRING_PTR(filename);
|
||
|
st_table *filename_expansion_hash = get_filename_expansion_hash();
|
||
|
if (st_lookup(filename_expansion_hash, path_key, &data)) {
|
||
|
return (VALUE)data;
|
||
|
} else {
|
||
|
return Qnil;
|
||
|
};
|
||
|
}
|
||
|
static void
|
||
|
rb_set_cached_expansion(VALUE filename, VALUE expanded)
|
||
|
{
|
||
|
st_data_t data = (st_data_t)expanded;
|
||
|
st_data_t path_key = (st_data_t)RSTRING_PTR(filename);
|
||
|
st_table *filename_expansion_hash = get_filename_expansion_hash();
|
||
|
st_insert(filename_expansion_hash, path_key, data);
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_locate_file(VALUE filename)
|
||
|
{
|
||
|
VALUE full_path = Qnil;
|
||
|
full_path = rb_get_cached_expansion(filename);
|
||
|
if (full_path != Qnil)
|
||
|
return full_path;
|
||
|
if (rb_path_is_relative(filename)) {
|
||
|
full_path = rb_locate_file_relative(filename);
|
||
|
} else if (rb_path_is_absolute(filename)) {
|
||
|
full_path = rb_locate_file_absolute(filename);
|
||
|
} else {
|
||
|
full_path = rb_locate_file_in_load_path(filename);
|
||
|
}
|
||
|
if (full_path != Qnil)
|
||
|
rb_set_cached_expansion(filename, full_path);
|
||
|
return full_path;
|
||
|
}
|
||
|
/*
|
||
|
* returns the path loaded, or nil if the file was already loaded. Raises
|
||
|
* LoadError if a file cannot be found.
|
||
|
*/
|
||
|
VALUE
|
||
|
rb_require_safe(VALUE fname, int safe)
|
||
|
{
|
||
|
VALUE path = Qnil;
|
||
|
volatile VALUE result = Qnil;
|
||
|
rb_thread_t *th = GET_THREAD();
|
||
|
volatile VALUE errinfo = th->errinfo;
|
||
|
int state;
|
||
|
struct {
|
||
|
int safe;
|
||
|
} volatile saved;
|
||
|
char *volatile ftptr = 0;
|
||
|
PUSH_TAG();
|
||
|
saved.safe = rb_safe_level();
|
||
|
if ((state = EXEC_TAG()) == 0) {
|
||
|
long handle;
|
||
|
int found;
|
||
|
rb_set_safe_level_force(safe);
|
||
|
FilePathValue(fname);
|
||
|
rb_set_safe_level_force(0);
|
||
|
path = rb_locate_file(fname);
|
||
|
if (safe >= 1 && OBJ_TAINTED(path)) {
|
||
|
rb_raise(rb_eSecurityError, "Loading from unsafe file %s", RSTRING_PTR(path));
|
||
|
}
|
||
|
result = Qfalse;
|
||
|
if (path == Qnil) {
|
||
|
load_failed(fname);
|
||
|
} else {
|
||
|
if (ftptr = load_lock(RSTRING_PTR(path))) { // Allows circular requires to work
|
||
|
if (!rb_file_has_been_required(path)) {
|
||
|
if (rb_file_is_ruby(path)) {
|
||
|
rb_load_internal(path, 0);
|
||
|
} else {
|
||
|
handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
|
||
|
path, 0, path);
|
||
|
rb_ary_push(ruby_dln_librefs, LONG2NUM(handle));
|
||
|
}
|
||
|
rb_provide_feature(path);
|
||
|
result = Qtrue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
POP_TAG();
|
||
|
load_unlock(ftptr, !state);
|
||
|
rb_set_safe_level_force(saved.safe);
|
||
|
if (state) {
|
||
|
JUMP_TAG(state);
|
||
|
}
|
||
|
if (NIL_P(result)) {
|
||
|
load_failed(fname);
|
||
|
}
|
||
|
th->errinfo = errinfo;
|
||
|
if (result == Qtrue) {
|
||
|
return path;
|
||
|
} else {
|
||
|
return Qnil;
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* call-seq:
|
||
|
* require(string) -> true or false
|
||
|
*
|
||
|
* Ruby tries to load the library named _string_, returning
|
||
|
* +true+ if successful. If the filename does not resolve to
|
||
|
* an absolute path, it will be searched for in the directories listed
|
||
|
* in <code>$:</code>. If the file has the extension ``.rb'', it is
|
||
|
* loaded as a source file; if the extension is ``.so'', ``.o'', or
|
||
|
* ``.dll'', or whatever the default shared library extension is on
|
||
|
* the current platform, Ruby loads the shared library as a Ruby
|
||
|
* extension. Otherwise, Ruby tries adding ``.rb'', ``.so'', and so on
|
||
|
* to the name. The name of the loaded feature is added to the array in
|
||
|
* <code>$"</code>. A feature will not be loaded if its name already
|
||
|
* appears in <code>$"</code>. The file name is converted to an absolute
|
||
|
* path, so ``<code>require 'a'; require './a'</code>'' will not load
|
||
|
* <code>a.rb</code> twice.
|
||
|
*
|
||
|
* require "my-library.rb"
|
||
|
* require "db-driver"
|
||
|
*/
|
||
|
VALUE
|
||
|
rb_f_require(VALUE obj, VALUE fname)
|
||
|
{
|
||
|
return rb_require_safe(fname, rb_safe_level()) == Qnil ? Qfalse : Qtrue;
|
||
|
}
|
||
|
static void
|
||
|
rb_rehash_loaded_features()
|
||
|
{
|
||
|
int i;
|
||
|
VALUE features;
|
||
|
VALUE feature;
|
||
|
st_table* loaded_features_hash = get_loaded_features_hash();
|
||
|
st_clear(loaded_features_hash);
|
||
|
features = get_loaded_features();
|
||
|
for (i = 0; i < RARRAY_LEN(features); ++i) {
|
||
|
feature = RARRAY_PTR(features)[i];
|
||
|
st_insert(
|
||
|
loaded_features_hash,
|
||
|
(st_data_t)ruby_strdup(RSTRING_PTR(feature)),
|
||
|
(st_data_t)rb_barrier_new());
|
||
|
}
|
||
|
}
|
||
|
static void
|
||
|
rb_clear_cached_expansions()
|
||
|
{
|
||
|
st_table* filename_expansion_hash = get_filename_expansion_hash();
|
||
|
st_clear(filename_expansion_hash);
|
||
|
}
|
||
|
static VALUE
|
||
|
rb_loaded_features_hook(int argc, VALUE *argv, VALUE self)
|
||
|
{
|
||
|
VALUE ret;
|
||
|
ret = rb_call_super(argc, argv);
|
||
|
rb_rehash_loaded_features();
|
||
|
rb_clear_cached_expansions();
|
||
|
return ret;
|
||
|
}
|
||
|
/*
|
||
|
* $LOADED_FEATURES is exposed publically as an array, but under the covers
|
||
|
* we also store this data in a hash for fast lookups. So that we can rebuild
|
||
|
* the hash whenever $LOADED_FEATURES is changed, we wrap the Array class
|
||
|
* in a proxy that intercepts all data-modifying methods and rebuilds the
|
||
|
* hash.
|
||
|
*
|
||
|
* Note that the list of intercepted methods is currently non-comprehensive
|
||
|
* --- it only covers modifications made by the ruby and rubyspec test suites.
|
||
|
*/
|
||
|
static void
|
||
|
define_loaded_features_proxy()
|
||
|
{
|
||
|
const char* methods_to_hook[] = {"push", "clear", "replace", "delete"};
|
||
|
unsigned int i;
|
||
|
rb_cLoadedFeaturesProxy = rb_define_class("LoadedFeaturesProxy", rb_cArray);
|
||
|
for (i = 0; i < CHAR_ARRAY_LEN(methods_to_hook); ++i) {
|
||
|
rb_define_method(
|
||
|
rb_cLoadedFeaturesProxy,
|
||
|
methods_to_hook[i],
|
||
|
rb_loaded_features_hook,
|
||
|
-1);
|
||
|
}
|
||
|
}
|
||
|
/* Should return true if the file has or is being loaded, but should
|
||
|
* not actually load the file.
|
||
|
*/
|
||
|
int
|
||
|
rb_feature_provided_2(VALUE fname)
|
||
|
{
|
||
|
VALUE full_path = rb_locate_file(fname);
|
||
|
if (
|
||
|
full_path != Qnil &&
|
||
|
(
|
||
|
rb_file_has_been_required(full_path) ||
|
||
|
rb_file_is_being_required(full_path)
|
||
|
)
|
||
|
) {
|
||
|
return TRUE;
|
||
|
} else {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* Deprecated, use rb_feature_provided_2
|
||
|
*/
|
||
|
int
|
||
|
rb_feature_provided(const char *feature, const char **loading)
|
||
|
{
|
||
|
VALUE fname = rb_str_new2(feature);
|
||
|
rb_feature_provided_2(fname);
|
||
|
}
|
||
|
void
|
||
|
Init_load()
|
||
|
{
|
||
|
#undef rb_intern
|
||
|
#define rb_intern(str) rb_intern2((str), strlen(str))
|
||
|
unsigned int j;
|
||
|
rb_vm_t *vm = GET_VM();
|
||
|
static const char var_load_path[] = "$:";
|
||
|
ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1);
|
||
| ... | ... | |
|
rb_define_virtual_variable("$\"", get_loaded_features, 0);
|
||
|
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0);
|
||
|
vm->loaded_features = rb_ary_new();
|
||
|
define_loaded_features_proxy();
|
||
|
vm->loaded_features = ary_new(rb_cLoadedFeaturesProxy, RARRAY_EMBED_LEN_MAX);
|
||
|
rb_define_global_function("load", rb_f_load, -1);
|
||
|
rb_define_global_function("require", rb_f_require, 1);
|
||
| ... | ... | |
|
ruby_dln_librefs = rb_ary_new();
|
||
|
rb_gc_register_mark_object(ruby_dln_librefs);
|
||
|
for (j = 0; j < CHAR_ARRAY_LEN(available_extensions); ++j) {
|
||
|
available_ext_rb_str[j] = rb_str_new2(available_extensions[j]);
|
||
|
rb_gc_register_mark_object(available_ext_rb_str[j]);
|
||
|
}
|
||
|
}
|
||
| test/ruby/test_require.rb | ||
|---|---|---|
|
[], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/,
|
||
|
bug3756)
|
||
|
end
|
||
|
def test_case_insensitive
|
||
|
load_path = $:.dup
|
||
|
loaded = $".dup
|
||
|
path = File.expand_path(__FILE__)
|
||
|
$:.unshift(File.dirname(path))
|
||
|
$".push(path) unless $".include?(path)
|
||
|
bug4255 = '[ruby-core:34297]'
|
||
|
assert_equal(false, $bug4255 ||= false, bug4255)
|
||
|
$bug4255 = true
|
||
|
f = File.basename(__FILE__, ".*").upcase
|
||
|
assert_equal(false, require(f))
|
||
|
ensure
|
||
|
$:.replace(load_path)
|
||
|
$".replace(loaded)
|
||
|
end if File.identical?(__FILE__, __FILE__.upcase)
|
||
|
def test_feature_is_reloaded_from_new_load_path_entry
|
||
|
# This is a bit of a weird test, but it is needed to ensure that some
|
||
|
# caching optimizations are working correctly.
|
||
|
load_path = $:.dup
|
||
|
loaded = $".dup
|
||
|
initial_length = loaded.length
|
||
|
Dir.mktmpdir do |tmp|
|
||
|
Dir.chdir(tmp) do
|
||
|
Dir.mkdir "a"
|
||
|
Dir.mkdir "b"
|
||
|
File.open("a/test.rb", "w") {|f| f.puts '' }
|
||
|
File.open("b/test.rb", "w") {|f| f.puts '' }
|
||
|
$".clear
|
||
|
$:.unshift(File.join(tmp, "b"))
|
||
|
require 'test.rb'
|
||
|
assert $"[0].include?('b/test.rb')
|
||
|
$".clear
|
||
|
$:.unshift(File.join(tmp, "a"))
|
||
|
require 'test.rb'
|
||
|
assert $"[0].include?('a/test.rb')
|
||
|
end
|
||
|
end
|
||
|
ensure
|
||
|
$:.replace(load_path)
|
||
|
$".replace(loaded)
|
||
|
end
|
||
|
end
|
||
| variable.c | ||
|---|---|---|
|
}
|
||
|
static VALUE
|
||
|
autoload_provided(VALUE arg)
|
||
|
autoload_provided(VALUE fname)
|
||
|
{
|
||
|
const char **p = (const char **)arg;
|
||
|
return rb_feature_provided(*p, p);
|
||
|
return rb_feature_provided_2(fname);
|
||
|
}
|
||
|
static VALUE
|
||
| ... | ... | |
|
loading = RSTRING_PTR(file);
|
||
|
safe = rb_safe_level();
|
||
|
rb_set_safe_level_force(0);
|
||
|
if (!rb_ensure(autoload_provided, (VALUE)&loading, reset_safe, (VALUE)safe)) {
|
||
|
if (!rb_ensure(autoload_provided, (VALUE)file, reset_safe, (VALUE)safe)) {
|
||
|
return load;
|
||
|
}
|
||
|
if (loadingpath && loading) {
|
||
| vm.c | ||
|---|---|---|
|
rb_mark_tbl(vm->loading_table);
|
||
|
}
|
||
|
if (vm->loaded_features_hash) {
|
||
|
rb_mark_tbl(vm->loaded_features_hash);
|
||
|
}
|
||
|
if (vm->filename_expansion_hash) {
|
||
|
rb_mark_tbl(vm->filename_expansion_hash);
|
||
|
}
|
||
|
mark_event_hooks(vm->event_hooks);
|
||
|
for (i = 0; i < RUBY_NSIG; i++) {
|
||
| vm_core.h | ||
|---|---|---|
|
* objects so do *NOT* mark this when you GC.
|
||
|
*/
|
||
|
struct RArray at_exit;
|
||
|
struct st_table *loaded_features_hash;
|
||
|
struct st_table *filename_expansion_hash;
|
||
|
} rb_vm_t;
|
||
|
typedef struct {
|
||
- « Previous
- 1
- 2
- Next »