Project

General

Profile

Feature #5767 » cache_expanded_load_path.patch

final version, `rb_find_file_safe` included, tests fixed. - funny_falcon (Yura Sokolov), 12/19/2011 04:07 AM

View differences:

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;
(4-4/5)