Feature #5767 » cache_expanded_load_path.patch
| file.c | ||
|---|---|---|
|     buflen = RSTRING_LEN(result),\ | ||
|     pend = p + buflen) | ||
| #define EXPAND_PATH()\ | ||
|     if ( !(abs_mode & FEP_DIR_EXPANDED) ) { \ | ||
| 	file_expand_path(dname, Qnil, abs_mode, result); \ | ||
|     } \ | ||
|     else { \ | ||
| 	size_t dlen = RSTRING_LEN(dname); \ | ||
| 	BUFCHECK(dlen > buflen); \ | ||
| 	strncpy(buf, RSTRING_PTR(dname), dlen + 1); \ | ||
| 	rb_str_set_len(result, dlen); \ | ||
| 	rb_enc_associate(result, rb_enc_check(result, dname)); \ | ||
| 	ENC_CODERANGE_CLEAR(result); \ | ||
|     } | ||
| VALUE | ||
| rb_home_dir(const char *user, VALUE result) | ||
| { | ||
| ... | ... | |
|     return result; | ||
| } | ||
| #define FEP_FILE_ABSOLUTE 1 | ||
| #define FEP_DIR_EXPANDED 2 | ||
| static VALUE | ||
| file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result) | ||
| { | ||
| ... | ... | |
|     BUFINIT(); | ||
|     tainted = OBJ_TAINTED(fname); | ||
|     if (s[0] == '~' && abs_mode == 0) {      /* execute only if NOT absolute_path() */ | ||
|     if (s[0] == '~' && !(abs_mode & FEP_FILE_ABSOLUTE)) {      /* execute only if NOT absolute_path() */ | ||
| 	long userlen = 0; | ||
| 	tainted = 1; | ||
| 	if (isdirsep(s[1]) || s[1] == '\0') { | ||
| ... | ... | |
| 	    /* specified drive, but not full path */ | ||
| 	    int same = 0; | ||
| 	    if (!NIL_P(dname) && !not_same_drive(dname, s[0])) { | ||
| 		file_expand_path(dname, Qnil, abs_mode, result); | ||
| 		EXPAND_PATH(); | ||
| 		BUFINIT(); | ||
| 		if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) { | ||
| 		    /* ok, same drive */ | ||
| ... | ... | |
| #endif | ||
|     else if (!rb_is_absolute_path(s)) { | ||
| 	if (!NIL_P(dname)) { | ||
| 	    file_expand_path(dname, Qnil, abs_mode, result); | ||
| 	    EXPAND_PATH(); | ||
| 	    BUFINIT(); | ||
| 	    rb_enc_associate(result, rb_enc_check(result, fname)); | ||
| 	} | ||
| ... | ... | |
| rb_file_absolute_path(VALUE fname, VALUE dname) | ||
| { | ||
|     check_expand_path_args(fname, dname); | ||
|     return file_expand_path(fname, dname, 1, EXPAND_PATH_BUFFER()); | ||
|     return file_expand_path(fname, dname, FEP_FILE_ABSOLUTE, EXPAND_PATH_BUFFER()); | ||
| } | ||
| /* | ||
| ... | ... | |
|     return rb_find_file_ext_safe(filep, ext, rb_safe_level()); | ||
| } | ||
| #define GET_LOAD_PATH() \ | ||
|     if (cached_expanded_load_path) { \ | ||
| 	RB_GC_GUARD(load_path) = rb_get_expanded_load_path(); \ | ||
| 	dirs_mode = FEP_DIR_EXPANDED; \ | ||
|     } \ | ||
|     else { \ | ||
| 	RB_GC_GUARD(load_path) = rb_get_load_path(); \ | ||
| 	dirs_mode = 0; \ | ||
|     } | ||
| int | ||
| rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level) | ||
| { | ||
|     const char *f = StringValueCStr(*filep); | ||
|     VALUE fname = *filep, load_path, tmp; | ||
|     long i, j, fnlen; | ||
|     int expanded = 0; | ||
|     int expanded = 0, dirs_mode; | ||
|     if (!ext[0]) return 0; | ||
| ... | ... | |
| 	rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | ||
|     } | ||
|     RB_GC_GUARD(load_path) = rb_get_load_path(); | ||
|     GET_LOAD_PATH(); | ||
|     if (!load_path) return 0; | ||
|     fname = rb_str_dup(*filep); | ||
| ... | ... | |
| 	    RB_GC_GUARD(str) = rb_get_path_check(str, safe_level); | ||
| 	    if (RSTRING_LEN(str) == 0) continue; | ||
| 	    file_expand_path(fname, str, 0, tmp); | ||
| 	    file_expand_path(fname, str, dirs_mode, tmp); | ||
| 	    if (file_load_ok(RSTRING_PTR(tmp))) { | ||
| 		*filep = copy_path_class(tmp, *filep); | ||
| 		return (int)(j+1); | ||
| ... | ... | |
| { | ||
|     VALUE tmp, load_path; | ||
|     const char *f = StringValueCStr(path); | ||
|     int expanded = 0; | ||
|     int expanded = 0, dirs_mode; | ||
|     if (f[0] == '~') { | ||
| 	tmp = file_expand_path_1(path); | ||
| ... | ... | |
| 	rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); | ||
|     } | ||
|     RB_GC_GUARD(load_path) = rb_get_load_path(); | ||
|     GET_LOAD_PATH(); | ||
|     if (load_path) { | ||
| 	long i; | ||
| ... | ... | |
| 	    VALUE str = RARRAY_PTR(load_path)[i]; | ||
| 	    RB_GC_GUARD(str) = rb_get_path_check(str, safe_level); | ||
| 	    if (RSTRING_LEN(str) > 0) { | ||
| 		file_expand_path(path, str, 0, tmp); | ||
| 		file_expand_path(path, str, dirs_mode, tmp); | ||
| 		f = RSTRING_PTR(tmp); | ||
| 		if (file_load_ok(f)) goto found; | ||
| 	    } | ||
| internal.h | ||
|---|---|---|
| /* load.c */ | ||
| VALUE rb_get_load_path(void); | ||
| VALUE rb_get_expanded_load_path(void); | ||
| RUBY_EXTERN int cached_expanded_load_path; | ||
| /* math.c */ | ||
| VALUE rb_math_atan2(VALUE, VALUE); | ||
| load.c | ||
|---|---|---|
| #include "ruby/ruby.h" | ||
| #include "ruby/util.h" | ||
| #include "ruby/encoding.h" | ||
| #include "internal.h" | ||
| #include "dln.h" | ||
| #include "eval_intern.h" | ||
| ... | ... | |
| #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | ||
| #endif | ||
| static const char *const loadable_ext[] = { | ||
|     ".rb", DLEXT, | ||
| #ifdef DLEXT2 | ||
| ... | ... | |
|     return load_path; | ||
| } | ||
| static VALUE rb_checked_expanded_cache(); | ||
| static void rb_set_expanded_cache(VALUE); | ||
| int cached_expanded_load_path = 1; | ||
| VALUE | ||
| rb_get_expanded_load_path(void) | ||
| { | ||
|     VALUE load_path = rb_get_load_path(); | ||
|     VALUE ary; | ||
|     long i; | ||
|     VALUE expanded = rb_checked_expanded_cache(); | ||
|     ary = rb_ary_new2(RARRAY_LEN(load_path)); | ||
|     for (i = 0; i < RARRAY_LEN(load_path); ++i) { | ||
| 	VALUE path = rb_file_expand_path(RARRAY_PTR(load_path)[i], Qnil); | ||
| 	rb_str_freeze(path); | ||
| 	rb_ary_push(ary, path); | ||
|     if ( !RTEST(expanded) ) { | ||
| 	VALUE load_path = rb_get_load_path(); | ||
| 	long i; | ||
| 	if (!load_path) return 0; | ||
| 	expanded = rb_ary_new2(RARRAY_LEN(load_path)); | ||
| 	for (i = 0; i < RARRAY_LEN(load_path); ++i) { | ||
| 	    VALUE path = rb_file_expand_path(RARRAY_PTR(load_path)[i], Qnil); | ||
| 	    rb_str_freeze(path); | ||
| 	    rb_ary_push(expanded, path); | ||
| 	} | ||
| 	if (cached_expanded_load_path) { | ||
| 	    rb_set_expanded_cache(expanded); | ||
| 	} | ||
|     } else { | ||
| 	expanded = rb_ary_dup(expanded); | ||
|     } | ||
|     rb_obj_freeze(ary); | ||
|     return ary; | ||
|     return expanded; | ||
| } | ||
| static VALUE | ||
| ... | ... | |
|     return rb_mod_autoload_p(klass, sym); | ||
| } | ||
| // $LOAD_PATH methods which invalidates cache | ||
| static const char *load_path_reset_cache_methods[] = { | ||
|     "[]=", "collect!", "compact!", "delete", | ||
|     "delete_if", "fill", "flatten!", "insert", "keep_if", | ||
|     "map!", "reject!", "replace", "select!", "shuffle!", | ||
|     "sort!", "sort_by!", "uniq!", NULL | ||
| }; | ||
| // $LOAD_PATH methods which sends also to cache | ||
| static const char *load_path_apply_to_cache_methods[] = { | ||
|     "clear", "delete_at", "pop", "reverse!", "rotate!", | ||
|     "shift", "slice!", NULL | ||
| }; | ||
| // $LOAD_PATH methods which sends to cache whith expanded arguments | ||
| static const char *load_path_apply_expanded_methods[] = { | ||
|     "<<", "push", "unshift", NULL | ||
| }; | ||
| static void | ||
| rb_reset_expanded_cache() | ||
| { | ||
|     GET_VM()->load_path_expanded_cache = 0; | ||
| } | ||
| static VALUE | ||
| rb_load_path_expanded_cache() | ||
| { | ||
|     VALUE cache = GET_VM()->load_path_expanded_cache; | ||
|     VALUE expanded = Qnil; | ||
|     if (RTEST(cache)) { | ||
| 	expanded = RARRAY_PTR(cache)[2]; | ||
|     } | ||
|     return expanded; | ||
| } | ||
| // Return cache only if we still in the same working directory | ||
| // and filesystem_encoding didn't change | ||
| // Invalidate cache otherwise | ||
| static VALUE | ||
| rb_checked_expanded_cache() | ||
| { | ||
|     VALUE cache = GET_VM()->load_path_expanded_cache; | ||
|     VALUE expanded = Qnil; | ||
|     if (RTEST(cache)) { | ||
| 	VALUE curwd = RARRAY_PTR(cache)[0]; | ||
| 	VALUE encindex = RARRAY_PTR(cache)[1]; | ||
| 	int cache_valid = rb_filesystem_encindex() == FIX2INT(encindex); | ||
| 	if ( cache_valid ) { | ||
| 	    cache_valid = curwd == Qtrue; | ||
| 	    if (!cache_valid ) { | ||
| 		char *cwd = my_getcwd(); | ||
| 		cache_valid = !strcmp(RSTRING_PTR(curwd), cwd); | ||
| 		xfree(cwd); | ||
| 	    } | ||
| 	} | ||
| 	if ( !cache_valid ) { | ||
| 	    rb_reset_expanded_cache(); | ||
| 	} else { | ||
| 	    expanded = RARRAY_PTR(cache)[2]; | ||
| 	} | ||
|     } | ||
|     RB_GC_GUARD(cache); | ||
|     return expanded; | ||
| } | ||
| static void | ||
| rb_set_expanded_cache(VALUE expanded) | ||
| { | ||
|     VALUE load_path = rb_get_load_path(); | ||
|     VALUE cache = rb_ary_new2(2); | ||
|     int i, lplen = RARRAY_LEN(load_path), has_relative = 0; | ||
|     char *d; | ||
|     for(i = 0; i < lplen && !has_relative; i++) { | ||
| 	d = RSTRING_PTR(RARRAY_PTR(load_path)[i]); | ||
| 	has_relative = !rb_is_absolute_path(d); | ||
|     } | ||
|     if (has_relative) { | ||
| 	char *cwd = my_getcwd(); | ||
| 	rb_ary_push(cache, rb_str_new_cstr(cwd)); | ||
| 	xfree(cwd); | ||
|     } else { | ||
| 	rb_ary_push(cache, Qtrue); | ||
|     } | ||
|     rb_ary_push(cache, INT2FIX(rb_filesystem_encindex())); | ||
|     rb_ary_push(cache, rb_ary_dup(expanded)); | ||
|     GET_VM()->load_path_expanded_cache = cache; | ||
| } | ||
| // Invalidating $LOAD_PATH methods implementation | ||
| static VALUE | ||
| rb_load_path_reset_cache_method(int argc, VALUE *argv, VALUE self) | ||
| { | ||
|     rb_reset_expanded_cache(); | ||
|     return rb_call_super(argc, argv); | ||
| } | ||
| // Proxying $LOAD_PATH methods implementation | ||
| static VALUE | ||
| rb_load_path_apply_to_cache_method(int argc, VALUE *argv, VALUE self) | ||
| { | ||
|     VALUE load_path_expanded = rb_load_path_expanded_cache(); | ||
|     if (RTEST(load_path_expanded)) { | ||
| 	ID func = rb_frame_this_func(); | ||
| 	rb_funcall2(load_path_expanded, func, argc, argv); | ||
|     } | ||
|     return rb_call_super(argc, argv); | ||
| } | ||
| // Proxying with expansion $LOAD_PATH methods implementation | ||
| static VALUE | ||
| rb_load_path_apply_expanded_method(int argc, VALUE *argv, VALUE self) | ||
| { | ||
|     // We call methods on cache only if we still in the same working directory | ||
|     VALUE load_path_expanded = rb_checked_expanded_cache(); | ||
|     if (RTEST(load_path_expanded)) { | ||
| 	int i, has_relative = 0; | ||
| 	ID func = rb_frame_this_func(); | ||
| 	VALUE expanded = rb_ary_new2(argc); | ||
| 	for(i = 0; i < argc; i++) { | ||
| 	    VALUE path = rb_get_path(argv[i]); | ||
| 	    has_relative = has_relative || !rb_is_absolute_path(RSTRING_PTR(path)); | ||
| 	    path = rb_file_expand_path(path, Qnil); | ||
| 	    rb_str_freeze(path); | ||
| 	    rb_ary_push(expanded, path); | ||
| 	} | ||
| 	if (has_relative) { | ||
| 	    VALUE cache = GET_VM()->load_path_expanded_cache; | ||
| 	    char *cwd = my_getcwd(); | ||
| 	    rb_ary_store(cache, 0, rb_str_new_cstr(cwd)); | ||
| 	    xfree(cwd); | ||
| 	} | ||
| 	rb_funcall2(load_path_expanded, func, argc, RARRAY_PTR(expanded)); | ||
| 	RB_GC_GUARD(expanded); | ||
|     } | ||
|     return rb_call_super(argc, argv); | ||
| } | ||
| // $LOAD_PATH.concat(ary) - special, we call push(*ary) instead | ||
| // cause I'm lazy a bit and wish not to rewrite method above second time :) | ||
| static VALUE | ||
| rb_load_path_concat(VALUE self, VALUE ary) | ||
| { | ||
|     ID push; | ||
|     CONST_ID(push, "push"); | ||
|     RB_GC_GUARD(ary); | ||
|     return rb_funcall2(self, push, RARRAY_LEN(ary), RARRAY_PTR(ary)); | ||
| } | ||
| static VALUE | ||
| rb_load_path_init(void) | ||
| { | ||
|     const char **name; | ||
|     VALUE load_path = rb_ary_new(); | ||
|     char *cached_flag; | ||
|     cached_flag = getenv("RUBY_CACHED_LOAD_PATH"); | ||
|     if (cached_flag != NULL) { | ||
| 	cached_expanded_load_path = atoi(cached_flag); | ||
|     } | ||
|     // Do all the magick if user did not disable it | ||
|     // with RUBY_CACHED_LOAD_PATH=0 environment variable | ||
|     if (cached_expanded_load_path) { | ||
| 	VALUE load_path_c = rb_singleton_class(load_path); | ||
| 	for(name = load_path_reset_cache_methods; *name; name++ ) { | ||
| 	    rb_define_method(load_path_c, *name, rb_load_path_reset_cache_method, -1); | ||
| 	} | ||
| 	for(name = load_path_apply_to_cache_methods; *name; name++ ) { | ||
| 	    rb_define_method(load_path_c, *name, rb_load_path_apply_to_cache_method, -1); | ||
| 	} | ||
| 	for(name = load_path_apply_expanded_methods; *name; name++ ) { | ||
| 	    rb_define_method(load_path_c, *name, rb_load_path_apply_expanded_method, -1); | ||
| 	} | ||
| 	rb_define_method(load_path_c, "concat", rb_load_path_concat, 1); | ||
|     } | ||
|     rb_reset_expanded_cache(); | ||
|     return load_path; | ||
| } | ||
| void | ||
| Init_load() | ||
| { | ||
| ... | ... | |
|     rb_define_hooked_variable(var_load_path, (VALUE*)vm, load_path_getter, rb_gvar_readonly_setter); | ||
|     rb_alias_variable(rb_intern("$-I"), id_load_path); | ||
|     rb_alias_variable(rb_intern("$LOAD_PATH"), id_load_path); | ||
|     vm->load_path = rb_ary_new(); | ||
|     vm->load_path = rb_load_path_init(); | ||
|     rb_define_virtual_variable("$\"", get_loaded_features, 0); | ||
|     rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0); | ||
| ruby.c | ||
|---|---|---|
|     const char sep = PATH_SEP_CHAR; | ||
|     const char *p, *s; | ||
|     VALUE load_path = GET_VM()->load_path; | ||
|     ID push; | ||
|     CONST_ID(push, "<<"); | ||
|     p = path; | ||
|     while (*p) { | ||
| ... | ... | |
| 	    p++; | ||
| 	if (!*p) break; | ||
| 	for (s = p; *s && *s != sep; s = CharNext(s)); | ||
| 	rb_ary_push(load_path, (*filter)(rubylib_mangled_path(p, s - p))); | ||
| 	rb_funcall(load_path, push, 1, (*filter)(rubylib_mangled_path(p, s - p))); | ||
| 	p = s; | ||
|     } | ||
| } | ||
| ... | ... | |
| ruby_init_loadpath_safe(int safe_level) | ||
| { | ||
|     VALUE load_path; | ||
|     ID id_initial_load_path_mark; | ||
|     ID id_initial_load_path_mark, push; | ||
|     extern const char ruby_initial_load_paths[]; | ||
|     const char *paths = ruby_initial_load_paths; | ||
| #if defined LOAD_RELATIVE | ||
| ... | ... | |
|     } | ||
|     id_initial_load_path_mark = rb_intern_const("@gem_prelude_index"); | ||
|     CONST_ID(push, "<<"); | ||
|     while (*paths) { | ||
| 	size_t len = strlen(paths); | ||
| 	VALUE path = RUBY_RELATIVE(paths, len); | ||
| 	rb_ivar_set(path, id_initial_load_path_mark, path); | ||
| 	rb_ary_push(load_path, path); | ||
| 	rb_funcall(load_path, push, 1, path); | ||
| 	paths += len + 1; | ||
|     } | ||
| vm.c | ||
|---|---|---|
| 	RUBY_MARK_UNLESS_NULL(vm->thgroup_default); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->mark_object_ary); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->load_path); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->load_path_expanded_cache); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->loaded_features); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->top_self); | ||
| 	RUBY_MARK_UNLESS_NULL(vm->coverages); | ||
| vm_core.h | ||
|---|---|---|
|     /* load */ | ||
|     VALUE top_self; | ||
|     VALUE load_path; | ||
|     VALUE load_path_expanded_cache; | ||
|     VALUE loaded_features; | ||
|     struct st_table *loading_table; | ||