Skip to content

Instantly share code, notes, and snippets.

@xaviershay
Created May 31, 2011 11:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xaviershay/58dbd6e72c1a1f47a415 to your computer and use it in GitHub Desktop.
Save xaviershay/58dbd6e72c1a1f47a415 to your computer and use it in GitHub Desktop.
Patches to improve ruby require times - see descriptions at https://gist.github.com/35060fbcefb25cf1a456
diff --git a/load.c b/load.c
index 547c115..1a02804 100644
--- a/load.c
+++ b/load.c
@@ -17,6 +17,238 @@ VALUE ruby_dln_librefs;
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
#endif
+static int rb_feature_exists(VALUE);
+
+static VALUE rb_locate_file(VALUE);
+static VALUE rb_locate_file_relative(VALUE);
+static VALUE rb_locate_file_absolute(VALUE);
+static VALUE rb_locate_file_in_load_path(VALUE);
+static VALUE rb_locate_rb_file_in_load_path(VALUE, VALUE, VALUE);
+static VALUE rb_locate_file_with_extension(VALUE, VALUE);
+static VALUE rb_locate_file_with_extensions(VALUE, VALUE);
+static int rb_path_is_absolute(VALUE);
+static int rb_path_is_relative(VALUE);
+static VALUE rb_file_extension(VALUE);
+
+VALUE rb_get_expanded_load_path();
+
+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(VALUE filename)
+{
+ VALUE full_path = Qnil;
+
+ 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);
+ }
+
+ return full_path;
+}
+
+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 VALUE
+rb_locate_file_relative(VALUE fname)
+{
+ VALUE path = rb_file_expand_path(fname, Qnil);
+ return rb_locate_file_with_extensions(path, rb_file_extension(path));
+}
+
+static int
+rb_path_is_absolute(VALUE path)
+{
+ // Delegate to file.c
+ return rb_is_absolute_path(RSTRING_PTR(path));
+}
+
+static VALUE
+rb_locate_file_absolute(VALUE fname)
+{
+ return rb_locate_file_with_extensions(fname, rb_file_extension(fname));
+}
+
+static VALUE
+rb_locate_file_in_load_path(VALUE path)
+{
+ long i;
+ VALUE load_path = rb_get_expanded_load_path();
+ VALUE expanded_file_name = Qnil;
+ VALUE base_file_name = Qnil;
+ VALUE sep = rb_str_new2("/");
+ VALUE base_extension = rb_file_extension(path);
+
+ if (RSTRING_LEN(base_extension) == 0) {
+ /* Do an initial loop through the load path only looking for .rb files.
+ * This is the most common case, so optimize for it. If not found, fall
+ * back so searching all extensions.
+ */
+ expanded_file_name = rb_locate_rb_file_in_load_path(path, load_path, sep);
+
+ if (expanded_file_name != Qnil) {
+ return expanded_file_name;
+ }
+ }
+
+ 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);
+
+ /* The .rb extension will be checked again in this call, which is redundant
+ * since it was checked in the loop above. This hasn't been optimized to
+ * keep the code cleaner.
+ */
+ expanded_file_name = rb_locate_file_with_extensions(base_file_name, base_extension);
+
+ if (expanded_file_name != Qnil) {
+ return expanded_file_name;
+ }
+ }
+ return Qnil;
+}
+
+/* This function is only used as an optimization in rb_locate_file_in_load_path */
+static VALUE
+rb_locate_rb_file_in_load_path(VALUE path, VALUE load_path, VALUE sep)
+{
+ long i;
+ VALUE base_file_name;
+ VALUE expanded_file_name;
+ VALUE rb_ext = rb_str_new2(".rb");
+
+ 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_extension(base_file_name, rb_ext);
+
+ if (expanded_file_name != Qnil) {
+ return expanded_file_name;
+ }
+ }
+ return Qnil;
+}
+
+static VALUE
+rb_locate_file_with_extension(VALUE base_file_name, VALUE extension) {
+ VALUE file_name_with_extension = rb_str_plus(
+ base_file_name,
+ extension);
+
+ if (rb_feature_exists(file_name_with_extension)) {
+ return file_name_with_extension;
+ } else {
+ return Qnil;
+ }
+}
+
+static VALUE
+rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) {
+ unsigned int j;
+ VALUE file_name_with_extension;
+ VALUE directory, basename;
+
+ 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 < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) {
+ // Also try loading 'dot.dot.bundle' for 'dot.dot'
+ // Also try loading 'test.1.rb' for 'test.1'
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+ return Qnil;
+}
+
+static VALUE
+rb_file_extension(VALUE path)
+{
+ return rb_funcall(rb_cFile, rb_intern("extname"), 1, path);
+}
+
+static int
+rb_feature_exists(VALUE expanded_path)
+{
+ return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue;
+}
+
+
static const char *const loadable_ext[] = {
".rb", DLEXT,
@@ -746,6 +978,7 @@ 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);
@@ -769,4 +1002,9 @@ Init_load()
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]);
+ }
}
diff --git a/load.c b/load.c
index 1a02804..b1e2c1e 100644
--- a/load.c
+++ b/load.c
@@ -17,6 +17,7 @@ VALUE ruby_dln_librefs;
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
#endif
+static VALUE get_loaded_features();
static int rb_feature_exists(VALUE);
static VALUE rb_locate_file(VALUE);
@@ -32,6 +33,14 @@ static VALUE rb_file_extension(VALUE);
VALUE rb_get_expanded_load_path();
+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();
+static VALUE loaded_features_proxy_new(VALUE);
+
+static st_table * get_loaded_features_hash(void);
+
const char *available_extensions[] = {
".rb",
DLEXT,
@@ -249,6 +258,90 @@ rb_feature_exists(VALUE expanded_path)
}
+/*
+ * $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);
+ }
+}
+
+static VALUE
+rb_loaded_features_hook(int argc, VALUE *argv, VALUE self)
+{
+ VALUE ret;
+ ret = rb_call_super(argc, argv);
+ rb_rehash_loaded_features();
+ return ret;
+}
+
+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 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;
+}
+
+/* This is cargo-culted from ary_alloc in array.c. I am sure there is
+ * a much better way to instantiate this class, I just don't know what it is.
+ */
+static VALUE
+loaded_features_proxy_new(VALUE klass)
+{
+ NEWOBJ(ary, struct RArray);
+ OBJSETUP(ary, klass, T_ARRAY);
+ FL_SET((ary), RARRAY_EMBED_FLAG);
+ RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK;
+ RBASIC(ary)->flags |= (0) << RARRAY_EMBED_LEN_SHIFT;
+
+ return (VALUE)ary;
+}
static const char *const loadable_ext[] = {
".rb", DLEXT,
@@ -990,7 +1083,10 @@ Init_load()
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 = loaded_features_proxy_new(rb_cLoadedFeaturesProxy);
rb_define_global_function("load", rb_f_load, -1);
rb_define_global_function("require", rb_f_require, 1);
diff --git a/vm.c b/vm.c
index bddb4dd..e0b22f3 100644
--- a/vm.c
+++ b/vm.c
@@ -1527,6 +1527,10 @@ rb_vm_mark(void *ptr)
rb_mark_tbl(vm->loading_table);
}
+ if (vm->loaded_features_hash) {
+ rb_mark_tbl(vm->loaded_features_hash);
+ }
+
mark_event_hooks(vm->event_hooks);
for (i = 0; i < RUBY_NSIG; i++) {
diff --git a/vm_core.h b/vm_core.h
index e302e62..4d1bc2b 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -324,6 +324,7 @@ typedef struct rb_vm_struct {
* objects so do *NOT* mark this when you GC.
*/
struct RArray at_exit;
+ struct st_table *loaded_features_hash;
} rb_vm_t;
typedef struct {
diff --git a/load.c b/load.c
index b1e2c1e..db227f0 100644
--- a/load.c
+++ b/load.c
@@ -17,9 +17,21 @@ VALUE ruby_dln_librefs;
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
#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 VALUE get_loaded_features();
static int rb_feature_exists(VALUE);
+static int rb_file_is_ruby(VALUE path);
+static int rb_file_has_been_required(VALUE);
+static int rb_file_is_being_required(VALUE);
+
+void rb_provide(const char *feature);
+static void rb_provide_feature(VALUE);
+
static VALUE rb_locate_file(VALUE);
static VALUE rb_locate_file_relative(VALUE);
static VALUE rb_locate_file_absolute(VALUE);
@@ -39,6 +51,7 @@ static VALUE rb_loaded_features_hook(int, VALUE*, VALUE);
static void define_loaded_features_proxy();
static VALUE loaded_features_proxy_new(VALUE);
+static st_table * get_loading_table(void);
static st_table * get_loaded_features_hash(void);
const char *available_extensions[] = {
@@ -66,6 +79,34 @@ const char *alternate_dl_extensions[] = {
#define CHAR_ARRAY_LEN(array) (sizeof(array) / sizeof(char*))
#define VALUE_ARRAY_LEN(array) (sizeof(array) / sizeof(VALUE))
+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_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_locate_file(VALUE filename)
{
@@ -538,40 +579,51 @@ rb_provided(const char *feature)
return rb_feature_provided(feature, 0);
}
+/* Should return true if the file has or is being loaded, but should
+ * not actually load the file.
+ */
int
-rb_feature_provided(const char *feature, const char **loading)
+rb_feature_provided_2(VALUE fname)
{
- const char *ext = strrchr(feature, '.');
- volatile VALUE fullpath = 0;
-
- 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;
- }
- else if (IS_SOEXT(ext) || IS_DLEXT(ext)) {
- if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE;
- return FALSE;
+ 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;
}
- }
- if (rb_feature_p(feature, 0, TRUE, FALSE, loading))
- return TRUE;
- return FALSE;
+}
+
+int
+rb_feature_provided(const char *feature, const char **loading)
+{
+ VALUE fname = rb_str_new2(feature);
+ return rb_feature_provided_2(fname);
}
static void
rb_provide_feature(VALUE feature)
{
+ st_table* loaded_features_hash;
+
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()
+ );
}
void
@@ -892,9 +944,14 @@ load_ext(VALUE path)
return (VALUE)dln_load(RSTRING_PTR(path));
}
+/*
+ * returns the path loaded, or false 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;
@@ -907,34 +964,41 @@ rb_require_safe(VALUE fname, int safe)
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)))) {
+ 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;
- }
- 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;
+ 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;
+ }
+ }
}
- rb_provide_feature(path);
- result = Qtrue;
- }
- }
}
POP_TAG();
load_unlock(ftptr, !state);
@@ -950,7 +1014,11 @@ rb_require_safe(VALUE fname, int safe)
th->errinfo = errinfo;
- return result;
+ if (result == Qtrue) {
+ return path;
+ } else {
+ return Qfalse;
+ }
}
VALUE
diff --git a/enumerator.c b/enumerator.c
index 7bb5ecd..b8ce698 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -1171,6 +1171,4 @@ Init_Enumerator(void)
id_rewind = rb_intern("rewind");
id_each = rb_intern("each");
sym_each = ID2SYM(id_each);
-
- rb_provide("enumerator.so"); /* for backward compatibility */
}
diff --git a/lib/enumerator.rb b/lib/enumerator.rb
new file mode 100644
index 0000000..b028c83
--- /dev/null
+++ b/lib/enumerator.rb
@@ -0,0 +1,3 @@
+# 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.
diff --git a/load.c b/load.c
index db227f0..496c11d 100644
--- a/load.c
+++ b/load.c
@@ -43,6 +43,10 @@ static int rb_path_is_absolute(VALUE);
static int rb_path_is_relative(VALUE);
static VALUE rb_file_extension(VALUE);
+static VALUE rb_get_cached_expansion(VALUE);
+static void rb_set_cached_expansion(VALUE, VALUE);
+static void rb_clear_cached_expansions();
+
VALUE rb_get_expanded_load_path();
static VALUE rb_cLoadedFeaturesProxy;
@@ -53,6 +57,7 @@ static VALUE loaded_features_proxy_new(VALUE);
static st_table * get_loading_table(void);
static st_table * get_loaded_features_hash(void);
+static st_table * get_filename_expansion_hash(void);
const char *available_extensions[] = {
".rb",
@@ -112,6 +117,11 @@ 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)) {
@@ -120,6 +130,9 @@ rb_locate_file(VALUE filename)
full_path = rb_locate_file_in_load_path(filename);
}
+ if (full_path != Qnil)
+ rb_set_cached_expansion(filename, full_path);
+
return full_path;
}
@@ -298,6 +311,30 @@ rb_feature_exists(VALUE expanded_path)
return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue;
}
+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);
+}
+
/*
* $LOADED_FEATURES is exposed publically as an array, but under the covers
@@ -331,6 +368,7 @@ 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;
}
@@ -356,6 +394,13 @@ rb_rehash_loaded_features()
}
}
+static void
+rb_clear_cached_expansions()
+{
+ st_table* filename_expansion_hash = get_filename_expansion_hash();
+ st_clear(filename_expansion_hash);
+}
+
static st_table *
get_loaded_features_hash(void)
{
@@ -369,6 +414,19 @@ get_loaded_features_hash(void)
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;
+}
+
/* This is cargo-culted from ary_alloc in array.c. I am sure there is
* a much better way to instantiate this class, I just don't know what it is.
*/
diff --git a/vm.c b/vm.c
index e0b22f3..40dc2eb 100644
--- a/vm.c
+++ b/vm.c
@@ -1531,6 +1531,10 @@ rb_vm_mark(void *ptr)
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++) {
diff --git a/vm_core.h b/vm_core.h
index 4d1bc2b..7818ec0 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -325,6 +325,7 @@ typedef struct rb_vm_struct {
*/
struct RArray at_exit;
struct st_table *loaded_features_hash;
+ struct st_table *filename_expansion_hash;
} rb_vm_t;
typedef struct {
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 96b1551..49a7952 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -339,4 +339,78 @@ class TestRequire < Test::Unit::TestCase
[], /\$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
+
+ def test_require_file_with_multiple_dots
+ load_path = $:.dup
+ loaded = $".dup
+ initial_length = loaded.length
+
+ Dir.mktmpdir do |tmp|
+ Dir.chdir(tmp) do
+ Dir.mkdir "a"
+ File.open("a/test.1.rb", "w") {|f| f.puts '' }
+
+ $".clear
+ $:.unshift(File.join(tmp, "a"))
+ require 'test.1'
+ assert $"[0].include?('test.1.rb')
+
+ $".clear
+ File.open("a/test.rb.rb", "w") {|f| f.puts '' }
+ File.open("a/test.rb", "w") {|f| f.puts '' }
+
+ require 'test.rb'
+ assert !$"[0].include?('test.rb.rb')
+ end
+ end
+ ensure
+ $:.replace(load_path)
+ $".replace(loaded)
+ end
end
diff --git a/load.c b/load.c
index 496c11d..776dd4d 100644
--- a/load.c
+++ b/load.c
@@ -159,7 +159,7 @@ rb_locate_file_relative(VALUE fname)
static int
rb_path_is_absolute(VALUE path)
{
- // Delegate to file.c
+ /* Delegate to file.c */
return rb_is_absolute_path(RSTRING_PTR(path));
}
@@ -268,8 +268,9 @@ rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) {
return base_file_name;
} else {
for (j = 0; j < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) {
- // Also try loading 'dot.dot.bundle' for 'dot.dot'
- // Also try loading 'test.1.rb' for 'test.1'
+ /* Also try loading 'dot.dot.bundle' for 'dot.dot'
+ * Also try loading 'test.1.rb' for 'test.1'
+ */
file_name_with_extension = rb_str_plus(
base_file_name,
available_ext_rb_str[j]);
@@ -280,8 +281,9 @@ rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) {
}
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
+ /* 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);
@@ -1038,7 +1040,7 @@ rb_require_safe(VALUE fname, int safe)
if (path == Qnil) {
load_failed(fname);
} else {
- if (ftptr = load_lock(RSTRING_PTR(path))) { // Allows circular requires to work
+ 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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment