Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JackDanger/c3ef1b5413844fc34336 to your computer and use it in GitHub Desktop.
Save JackDanger/c3ef1b5413844fc34336 to your computer and use it in GitHub Desktop.
Xavier Shay's Kernel#require speedup
diff --git a/.gitignore b/.gitignore
index 57557c9..7955376 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,3 +129,4 @@ y.tab.c
# /win32/
/win32/*.ico
+ext/win32ole/.document
diff --git a/array.c b/array.c
index efdf77f..ea04665 100644
--- a/array.c
+++ b/array.c
@@ -306,7 +306,7 @@ ary_alloc(VALUE klass)
return (VALUE)ary;
}
-static VALUE
+VALUE
ary_new(VALUE klass, long capa)
{
VALUE ary;
diff --git a/enc/make_encmake.rb b/enc/make_encmake.rb
index ed36803..8784cb4 100755
--- a/enc/make_encmake.rb
+++ b/enc/make_encmake.rb
@@ -3,10 +3,8 @@
dir = File.expand_path("../..", __FILE__)
$:.unshift(dir)
$:.unshift(".")
-if $".grep(/mkmf/).empty?
- $" << "mkmf.rb"
- load File.expand_path("lib/mkmf.rb", dir)
-end
+$:.unshift(dir + '/lib')
+require 'mkmf'
require 'erb'
CONFIG["MAKEDIRS"] ||= '$(MINIRUBY) -run -e mkdir -- -p'
diff --git a/enumerator.c b/enumerator.c
index b7bf63b..76cfe4b 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -1176,6 +1176,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/ext/extmk.rb b/ext/extmk.rb
index de6e037..0069020 100755
--- a/ext/extmk.rb
+++ b/ext/extmk.rb
@@ -36,8 +36,8 @@ require 'rbconfig'
$topdir = "."
$top_srcdir = srcdir
-$" << "mkmf.rb"
-load File.expand_path("lib/mkmf.rb", srcdir)
+$:.unshift(srcdir + '/lib')
+require 'mkmf'
require 'optparse/shellwords'
def sysquote(x)
diff --git a/file.c b/file.c
index 6e5cb4a..5a73e36 100644
--- a/file.c
+++ b/file.c
@@ -1282,7 +1282,7 @@ rb_file_chardev_p(VALUE obj, VALUE fname)
* Return <code>true</code> if the named file exists.
*/
-static VALUE
+VALUE
rb_file_exist_p(VALUE obj, VALUE fname)
{
struct stat st;
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 163ec4c..f3a0b07 100644
--- a/load.c
+++ b/load.c
@@ -19,14 +19,73 @@ VALUE ruby_dln_librefs;
#endif
-static const char *const loadable_ext[] = {
- ".rb", DLEXT,
+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);
+
+/*
+ * These functions are all conceptually related, and could 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, VALUE);
+static int rb_path_is_absolute(VALUE);
+static int rb_path_is_relative(VALUE);
+VALUE rb_get_expanded_load_path();
+
+/*
+ * These functions are all conceptually related, and could 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
+VALUE rb_file_exist_p(VALUE, VALUE); // file.c
+
+const char *available_extensions[] = {
+ ".rb",
+ DLEXT,
#ifdef DLEXT2
- DLEXT2,
+ DLEXT2,
#endif
- 0
+ ""
};
+#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))
+
VALUE
rb_get_load_path(void)
{
@@ -34,6 +93,32 @@ rb_get_load_path(void)
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)
{
@@ -69,196 +154,30 @@ get_loading_table(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;
- long plen;
- const char *e;
-
- if(vlen < len) return 0;
- if (!strncmp(name+(vlen-len),feature,len)){
- plen = vlen - len - 1;
- } else {
- for (e = name + vlen; name != e && *e != '.' && *e != '/'; --e);
- if (*e!='.' ||
- e-name < len ||
- strncmp(e-len,feature,len) )
- return 0;
- plen = e - name - len - 1;
- }
- 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 (n != plen ) continue;
- if (n && (strncmp(name, s, n) || name[n] != '/')) 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_fast(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;
-}
+ 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());
-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);
+ rb_ary_push(get_loaded_features(), feature);
}
void
@@ -430,43 +349,6 @@ load_unlock(const char *ftptr, int done)
/*
- * call-seq:
- * require(name) -> true or false
- *
- * Loads the given +name+, returning +true+ if successful and +false+ if the
- * feature is already loaded.
- *
- * If the filename does not resolve to an absolute path, it will be searched
- * for in the directories listed in <code>$LOAD_PATH</code> (<code>$:</code>).
- *
- * If the filename has the extension ".rb", it is loaded as a source file; if
- * the extension is ".so", ".o", or ".dll", or the default shared library
- * extension 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 until found. If the file named cannot be found, a LoadError
- * will be raised.
- *
- * For Ruby extensions the filename given may use any shared library
- * extension. For example, on Linux the socket extension is "socket.so" and
- * <code>require 'socket.dll'</code> will load the socket extension.
- *
- * The absolute path of the loaded file is added to
- * <code>$LOADED_FEATURES</code> (<code>$"</code>). A file will not be
- * loaded again if its path already appears in <code>$"</code>. For example,
- * <code>require 'a'; require './a'</code> will not load <code>a.rb</code>
- * again.
- *
- * 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
*
@@ -485,93 +367,6 @@ rb_f_require_relative(VALUE obj, VALUE fname)
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_filesystem_str_new_cstr(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_filesystem_str_new_cstr(loading);
- return 's';
- }
- tmp = rb_str_subseq(fname, 0, 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_filesystem_str_new_cstr(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_filesystem_str_new_cstr(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)
{
@@ -588,67 +383,6 @@ load_ext(VALUE path)
}
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);
@@ -739,6 +473,454 @@ rb_f_autoload(VALUE obj, VALUE sym, VALUE file)
return rb_mod_autoload(klass, sym, file);
}
+static int
+rb_feature_exists(VALUE expanded_path)
+{
+ return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue;
+}
+
+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 VALUE
+rb_locate_file_absolute(VALUE fname)
+{
+ return rb_locate_file_with_extensions(fname, rb_file_extension(fname));
+}
+
+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));
+}
+
+/* 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_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;
+}
+
+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);
+}
+
/*
* call-seq:
* autoload?(name) -> String or nil
@@ -766,6 +948,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);
@@ -777,7 +960,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 = 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);
@@ -789,4 +975,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/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 58a9ee2..3b5671c 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -340,6 +340,80 @@ class TestRequire < Test::Unit::TestCase
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
+
def test_loaded_features_encoding
bug6377 = '[ruby-core:44750]'
loadpath = $:.dup
diff --git a/variable.c b/variable.c
index ba47d26..bb37cfc 100644
--- a/variable.c
+++ b/variable.c
@@ -19,6 +19,7 @@
#include "constant.h"
#include "internal.h"
+VALUE rb_feature_provided_2(VALUE);
st_table *rb_global_tbl;
st_table *rb_class_tbl;
static ID autoload, classpath, tmp_classpath, classid;
@@ -1492,10 +1493,9 @@ autoload_delete(VALUE mod, ID id)
}
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
@@ -1528,7 +1528,7 @@ autoload_node(VALUE mod, ID id, const char **loadingpath)
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) {
diff --git a/vm.c b/vm.c
index d2ef450..06c2484 100644
--- a/vm.c
+++ b/vm.c
@@ -1601,6 +1601,14 @@ rb_vm_mark(void *ptr)
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++) {
diff --git a/vm_core.h b/vm_core.h
index 6530fbf..56d58d5 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -327,6 +327,9 @@ typedef struct rb_vm_struct {
* 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 {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment