Created
May 27, 2011 23:53
-
-
Save xaviershay/996418 to your computer and use it in GitHub Desktop.
Patch to make ruby require much faster than it used to be.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 9664626..9a1cb85 100644 | |
--- a/array.c | |
+++ b/array.c | |
@@ -305,7 +305,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 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/ext/extmk.rb b/ext/extmk.rb | |
index 3aa8154..846c8fa 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 3746009..65a71b8 100644 | |
--- a/file.c | |
+++ b/file.c | |
@@ -1270,7 +1270,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 547c115..cde81bc 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -18,14 +18,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, | |
+#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, | |
+ DLEXT2 | |
#endif | |
- 0 | |
}; | |
+#define CHAR_ARRAY_LEN(array) (sizeof(array) / sizeof(char*)) | |
+#define VALUE_ARRAY_LEN(array) (sizeof(array) / sizeof(VALUE)) | |
+ | |
VALUE | |
rb_get_load_path(void) | |
{ | |
@@ -33,6 +92,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) | |
{ | |
@@ -68,185 +153,32 @@ 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; | |
- | |
- 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; | |
- } | |
- else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { | |
- if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE; | |
- return FALSE; | |
+ if (OBJ_FROZEN(get_loaded_features())) { | |
+ rb_raise(rb_eRuntimeError, | |
+ "$LOADED_FEATURES is frozen; cannot append feature"); | |
} | |
- } | |
- 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 | |
@@ -418,34 +350,6 @@ load_unlock(const char *ftptr, int done) | |
/* | |
- * 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 | |
* | |
@@ -465,92 +369,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_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) | |
@@ -568,67 +386,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); | |
@@ -741,11 +498,461 @@ rb_f_autoload_p(VALUE obj, VALUE sym) | |
return rb_mod_autoload_p(klass, sym); | |
} | |
+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); | |
+} | |
+ | |
+ | |
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); | |
@@ -757,7 +964,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); | |
@@ -769,4 +979,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 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/variable.c b/variable.c | |
index 426d58f..8758375 100644 | |
--- a/variable.c | |
+++ b/variable.c | |
@@ -19,6 +19,7 @@ | |
#include "constant.h" | |
#include "internal.h" | |
+VALUE rb_feature_provided_2(VALUE); | |
void rb_vm_change_state(void); | |
void rb_vm_inc_const_missing_count(void); | |
@@ -1489,10 +1490,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 | |
@@ -1525,7 +1525,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 bddb4dd..a96b006 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1527,6 +1527,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 e302e62..d5f5fb7 100644 | |
--- a/vm_core.h | |
+++ b/vm_core.h | |
@@ -324,6 +324,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