bootsnap 1.1.8 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +121 -0
  3. data/README.md +72 -14
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +283 -105
  6. data/ext/bootsnap/extconf.rb +21 -14
  7. data/lib/bootsnap.rb +95 -14
  8. data/lib/bootsnap/bundler.rb +6 -3
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  11. data/lib/bootsnap/compile_cache.rb +32 -4
  12. data/lib/bootsnap/compile_cache/iseq.rb +32 -15
  13. data/lib/bootsnap/compile_cache/yaml.rb +97 -40
  14. data/lib/bootsnap/explicit_require.rb +2 -1
  15. data/lib/bootsnap/load_path_cache.rb +33 -20
  16. data/lib/bootsnap/load_path_cache/cache.rb +65 -29
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +70 -53
  19. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +18 -0
  20. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +148 -0
  21. data/lib/bootsnap/load_path_cache/path.rb +8 -7
  22. data/lib/bootsnap/load_path_cache/path_scanner.rb +61 -39
  23. data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
  24. data/lib/bootsnap/load_path_cache/store.rb +27 -14
  25. data/lib/bootsnap/setup.rb +3 -40
  26. data/lib/bootsnap/version.rb +2 -1
  27. metadata +26 -29
  28. data/.gitignore +0 -17
  29. data/.rubocop.yml +0 -20
  30. data/.travis.yml +0 -4
  31. data/CODE_OF_CONDUCT.md +0 -74
  32. data/CONTRIBUTING.md +0 -21
  33. data/Gemfile +0 -8
  34. data/Rakefile +0 -11
  35. data/bin/console +0 -14
  36. data/bin/setup +0 -8
  37. data/bin/testunit +0 -8
  38. data/bootsnap.gemspec +0 -39
  39. data/dev.yml +0 -10
  40. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -75
@@ -1,15 +1,43 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module CompileCache
4
+ Error = Class.new(StandardError)
5
+ PermissionError = Class.new(Error)
6
+
3
7
  def self.setup(cache_dir:, iseq:, yaml:)
4
8
  if iseq
5
- require_relative 'compile_cache/iseq'
6
- Bootsnap::CompileCache::ISeq.install!(cache_dir)
9
+ if supported?
10
+ require_relative('compile_cache/iseq')
11
+ Bootsnap::CompileCache::ISeq.install!(cache_dir)
12
+ elsif $VERBOSE
13
+ warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
14
+ end
7
15
  end
8
16
 
9
17
  if yaml
10
- require_relative 'compile_cache/yaml'
11
- Bootsnap::CompileCache::YAML.install!(cache_dir)
18
+ if supported?
19
+ require_relative('compile_cache/yaml')
20
+ Bootsnap::CompileCache::YAML.install!(cache_dir)
21
+ elsif $VERBOSE
22
+ warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
23
+ end
12
24
  end
13
25
  end
26
+
27
+ def self.permission_error(path)
28
+ cpath = Bootsnap::CompileCache::ISeq.cache_dir
29
+ raise(
30
+ PermissionError,
31
+ "bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
32
+ "(or, less likely, doesn't have permission to read '#{path}')",
33
+ )
34
+ end
35
+
36
+ def self.supported?
37
+ # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
38
+ RUBY_ENGINE == 'ruby' &&
39
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
40
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
41
+ end
14
42
  end
15
43
  end
@@ -1,31 +1,49 @@
1
- require 'bootsnap/bootsnap'
2
- require 'zlib'
1
+ # frozen_string_literal: true
2
+ require('bootsnap/bootsnap')
3
+ require('zlib')
3
4
 
4
5
  module Bootsnap
5
6
  module CompileCache
6
7
  module ISeq
7
8
  class << self
8
- attr_accessor :cache_dir
9
+ attr_accessor(:cache_dir)
9
10
  end
10
11
 
11
12
  def self.input_to_storage(_, path)
12
13
  RubyVM::InstructionSequence.compile_file(path).to_binary
13
14
  rescue SyntaxError
14
- raise Uncompilable, 'syntax error'
15
+ raise(Uncompilable, 'syntax error')
15
16
  end
16
17
 
17
- def self.storage_to_output(binary)
18
+ def self.storage_to_output(binary, _args)
18
19
  RubyVM::InstructionSequence.load_from_binary(binary)
19
20
  rescue RuntimeError => e
20
21
  if e.message == 'broken binary format'
21
- STDERR.puts "[Bootsnap::CompileCache] warning: rejecting broken binary"
22
- return nil
22
+ STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
23
+ nil
23
24
  else
24
25
  raise
25
26
  end
26
27
  end
27
28
 
28
- def self.input_to_output(_)
29
+ def self.fetch(path, cache_dir: ISeq.cache_dir)
30
+ Bootsnap::CompileCache::Native.fetch(
31
+ cache_dir,
32
+ path.to_s,
33
+ Bootsnap::CompileCache::ISeq,
34
+ nil,
35
+ )
36
+ end
37
+
38
+ def self.precompile(path, cache_dir: ISeq.cache_dir)
39
+ Bootsnap::CompileCache::Native.precompile(
40
+ cache_dir,
41
+ path.to_s,
42
+ Bootsnap::CompileCache::ISeq,
43
+ )
44
+ end
45
+
46
+ def self.input_to_output(_data, _kwargs)
29
47
  nil # ruby handles this
30
48
  end
31
49
 
@@ -34,14 +52,12 @@ module Bootsnap
34
52
  # Having coverage enabled prevents iseq dumping/loading.
35
53
  return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
36
54
 
37
- Bootsnap::CompileCache::Native.fetch(
38
- Bootsnap::CompileCache::ISeq.cache_dir,
39
- path.to_s,
40
- Bootsnap::CompileCache::ISeq
41
- )
55
+ Bootsnap::CompileCache::ISeq.fetch(path.to_s)
56
+ rescue Errno::EACCES
57
+ Bootsnap::CompileCache.permission_error(path)
42
58
  rescue RuntimeError => e
43
59
  if e.message =~ /unmatched platform/
44
- puts "unmatched platform for file #{path}"
60
+ puts("unmatched platform for file #{path}")
45
61
  end
46
62
  raise
47
63
  end
@@ -57,12 +73,13 @@ module Bootsnap
57
73
  crc = Zlib.crc32(option.inspect)
58
74
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
59
75
  end
76
+ compile_option_updated
60
77
 
61
78
  def self.install!(cache_dir)
62
79
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
63
80
  Bootsnap::CompileCache::ISeq.compile_option_updated
64
81
  class << RubyVM::InstructionSequence
65
- prepend InstructionSequenceMixin
82
+ prepend(InstructionSequenceMixin)
66
83
  end
67
84
  end
68
85
  end
@@ -1,57 +1,114 @@
1
- require 'bootsnap/bootsnap'
1
+ # frozen_string_literal: true
2
+ require('bootsnap/bootsnap')
2
3
 
3
4
  module Bootsnap
4
5
  module CompileCache
5
6
  module YAML
6
7
  class << self
7
- attr_accessor :msgpack_factory
8
- end
9
-
10
- def self.input_to_storage(contents, _)
11
- raise Uncompilable if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
13
- msgpack_factory.packer.write(obj).to_s
14
- rescue NoMethodError, RangeError
15
- # if the object included things that we can't serialize, fall back to
16
- # Marshal. It's a bit slower, but can encode anything yaml can.
17
- # NoMethodError is unexpected types; RangeError is Bignums
18
- return Marshal.dump(obj)
19
- end
8
+ attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
20
9
 
21
- def self.storage_to_output(data)
22
- # This could have a meaning in messagepack, and we're being a little lazy
23
- # about it. -- but a leading 0x04 would indicate the contents of the YAML
24
- # is a positive integer, which is rare, to say the least.
25
- if data[0] == 0x04.chr && data[1] == 0x08.chr
26
- Marshal.load(data)
27
- else
28
- msgpack_factory.unpacker.feed(data).read
10
+ def input_to_storage(contents, _)
11
+ raise(Uncompilable) if contents.index("!ruby/object")
12
+ obj = ::YAML.load(contents)
13
+ msgpack_factory.dump(obj)
14
+ rescue NoMethodError, RangeError
15
+ # The object included things that we can't serialize
16
+ raise(Uncompilable)
29
17
  end
30
- end
31
-
32
- def self.input_to_output(data)
33
- ::YAML.load(data)
34
- end
35
18
 
36
- def self.install!(cache_dir)
37
- require 'yaml'
38
- require 'msgpack'
19
+ def storage_to_output(data, kwargs)
20
+ if kwargs && kwargs.key?(:symbolize_names)
21
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
22
+ end
23
+ msgpack_factory.load(data, kwargs)
24
+ end
39
25
 
40
- # MessagePack serializes symbols as strings by default.
41
- # We want them to roundtrip cleanly, so we use a custom factory.
42
- # see: https://github.com/msgpack/msgpack-ruby/pull/122
43
- factory = MessagePack::Factory.new
44
- factory.register_type(0x00, Symbol)
45
- Bootsnap::CompileCache::YAML.msgpack_factory = factory
26
+ def input_to_output(data, kwargs)
27
+ ::YAML.load(data, **(kwargs || {}))
28
+ end
46
29
 
47
- klass = class << ::YAML; self; end
48
- klass.send(:define_method, :load_file) do |path|
49
- Bootsnap::CompileCache::Native.fetch(
30
+ def precompile(path, cache_dir: YAML.cache_dir)
31
+ Bootsnap::CompileCache::Native.precompile(
50
32
  cache_dir,
51
33
  path.to_s,
52
- Bootsnap::CompileCache::YAML
34
+ Bootsnap::CompileCache::YAML,
53
35
  )
54
36
  end
37
+
38
+ def install!(cache_dir)
39
+ self.cache_dir = cache_dir
40
+ init!
41
+ ::YAML.singleton_class.prepend(Patch)
42
+ end
43
+
44
+ def init!
45
+ require('yaml')
46
+ require('msgpack')
47
+ require('date')
48
+
49
+ # MessagePack serializes symbols as strings by default.
50
+ # We want them to roundtrip cleanly, so we use a custom factory.
51
+ # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
+ factory = MessagePack::Factory.new
53
+ factory.register_type(0x00, Symbol)
54
+
55
+ if defined? MessagePack::Timestamp
56
+ factory.register_type(
57
+ MessagePack::Timestamp::TYPE, # or just -1
58
+ Time,
59
+ packer: MessagePack::Time::Packer,
60
+ unpacker: MessagePack::Time::Unpacker
61
+ )
62
+
63
+ marshal_fallback = {
64
+ packer: ->(value) { Marshal.dump(value) },
65
+ unpacker: ->(payload) { Marshal.load(payload) },
66
+ }
67
+ {
68
+ Date => 0x01,
69
+ Regexp => 0x02,
70
+ }.each do |type, code|
71
+ factory.register_type(code, type, marshal_fallback)
72
+ end
73
+ end
74
+
75
+ self.msgpack_factory = factory
76
+
77
+ self.supported_options = []
78
+ params = ::YAML.method(:load).parameters
79
+ if params.include?([:key, :symbolize_names])
80
+ self.supported_options << :symbolize_names
81
+ end
82
+ if params.include?([:key, :freeze])
83
+ if factory.load(factory.dump('yaml'), freeze: true).frozen?
84
+ self.supported_options << :freeze
85
+ end
86
+ end
87
+ self.supported_options.freeze
88
+ end
89
+ end
90
+
91
+ module Patch
92
+ def load_file(path, *args)
93
+ return super if args.size > 1
94
+ if kwargs = args.first
95
+ return super unless kwargs.is_a?(Hash)
96
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
97
+ end
98
+
99
+ begin
100
+ ::Bootsnap::CompileCache::Native.fetch(
101
+ Bootsnap::CompileCache::YAML.cache_dir,
102
+ File.realpath(path),
103
+ ::Bootsnap::CompileCache::YAML,
104
+ kwargs,
105
+ )
106
+ rescue Errno::EACCES
107
+ ::Bootsnap::CompileCache.permission_error(path)
108
+ end
109
+ end
110
+
111
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
55
112
  end
56
113
  end
57
114
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -5,7 +6,7 @@ module Bootsnap
5
6
  DLEXT = RbConfig::CONFIG['DLEXT']
6
7
 
7
8
  def self.from_self(feature)
8
- require_relative "../#{feature}"
9
+ require_relative("../#{feature}")
9
10
  end
10
11
 
11
12
  def self.from_rubylibdir(feature)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bootsnap
2
4
  module LoadPathCache
3
5
  ReturnFalse = Class.new(StandardError)
@@ -7,6 +9,11 @@ module Bootsnap
7
9
  DOT_SO = '.so'
8
10
  SLASH = '/'
9
11
 
12
+ # If a NameError happens several levels deep, don't re-handle it
13
+ # all the way up the chain: mark it once and bubble it up without
14
+ # more retries.
15
+ ERROR_TAG_IVAR = :@__bootsnap_rescued
16
+
10
17
  DL_EXTENSIONS = ::RbConfig::CONFIG
11
18
  .values_at('DLEXT', 'DLEXT2')
12
19
  .reject { |ext| !ext || ext.empty? }
@@ -21,32 +28,38 @@ module Bootsnap
21
28
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
22
29
 
23
30
  class << self
24
- attr_reader :load_path_cache, :autoload_paths_cache
31
+ attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
32
+
33
+ def setup(cache_path:, development_mode:)
34
+ unless supported?
35
+ warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
36
+ return
37
+ end
25
38
 
26
- def setup(cache_path:, development_mode:, active_support: true)
27
39
  store = Store.new(cache_path)
28
40
 
41
+ @loaded_features_index = LoadedFeaturesIndex.new
42
+ @realpath_cache = RealpathCache.new
43
+
29
44
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
30
- require_relative 'load_path_cache/core_ext/kernel_require'
31
-
32
- if active_support
33
- # this should happen after setting up the initial cache because it
34
- # loads a lot of code. It's better to do after +require+ is optimized.
35
- require 'active_support/dependencies'
36
- @autoload_paths_cache = Cache.new(
37
- store,
38
- ::ActiveSupport::Dependencies.autoload_paths,
39
- development_mode: development_mode
40
- )
41
- require_relative 'load_path_cache/core_ext/active_support'
42
- end
45
+ require_relative('load_path_cache/core_ext/kernel_require')
46
+ require_relative('load_path_cache/core_ext/loaded_features')
47
+ end
48
+
49
+ def supported?
50
+ RUBY_ENGINE == 'ruby' &&
51
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
43
52
  end
44
53
  end
45
54
  end
46
55
  end
47
56
 
48
- require_relative 'load_path_cache/path_scanner'
49
- require_relative 'load_path_cache/path'
50
- require_relative 'load_path_cache/cache'
51
- require_relative 'load_path_cache/store'
52
- require_relative 'load_path_cache/change_observer'
57
+ if Bootsnap::LoadPathCache.supported?
58
+ require_relative('load_path_cache/path_scanner')
59
+ require_relative('load_path_cache/path')
60
+ require_relative('load_path_cache/cache')
61
+ require_relative('load_path_cache/store')
62
+ require_relative('load_path_cache/change_observer')
63
+ require_relative('load_path_cache/loaded_features_index')
64
+ require_relative('load_path_cache/realpath_cache')
65
+ end
@@ -1,4 +1,6 @@
1
- require_relative '../explicit_require'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative('../explicit_require')
2
4
 
3
5
  module Bootsnap
4
6
  module LoadPathCache
@@ -8,16 +10,16 @@ module Bootsnap
8
10
  def initialize(store, path_obj, development_mode: false)
9
11
  @development_mode = development_mode
10
12
  @store = store
11
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
12
- @path_obj = path_obj
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
13
15
  @has_relative_paths = nil
14
16
  reinitialize
15
17
  end
16
18
 
17
- # Does this directory exist as a child of one of the path items?
18
- # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], has_dir?("c/d")
19
- # is true.
20
- def has_dir?(dir)
19
+ # What is the path item that contains the dir as child?
20
+ # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
21
+ # is "/a/b".
22
+ def load_dir(dir)
21
23
  reinitialize if stale?
22
24
  @mutex.synchronize { @dirs[dir] }
23
25
  end
@@ -44,9 +46,9 @@ module Bootsnap
44
46
  # loadpath.
45
47
  def find(feature)
46
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
47
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
48
50
  return feature if absolute_path?(feature)
49
- return File.expand_path(feature) if feature.start_with?('./')
51
+ return expand_path(feature) if feature.start_with?('./')
50
52
  @mutex.synchronize do
51
53
  x = search_index(feature)
52
54
  return x if x
@@ -56,7 +58,7 @@ module Bootsnap
56
58
  # returns false as if it were already loaded; however, there is no
57
59
  # file to find on disk. We've pre-built a list of these, and we
58
60
  # return false if any of them is loaded.
59
- raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
61
+ raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
60
62
 
61
63
  # The feature wasn't found on our preliminary search through the index.
62
64
  # We resolve this differently depending on what the extension was.
@@ -65,7 +67,7 @@ module Bootsnap
65
67
  # native dynamic extension, e.g. .bundle or .so), we know it was a
66
68
  # failure and there's nothing more we can do to find the file.
67
69
  # no extension, .rb, (.bundle or .so)
68
- when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
70
+ when '', *CACHED_EXTENSIONS
69
71
  nil
70
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
71
73
  # is '.bundle'. This is where we handle that case.
@@ -73,14 +75,21 @@ module Bootsnap
73
75
  x = search_index(feature[0..-4] + DLEXT)
74
76
  return x if x
75
77
  if DLEXT2
76
- search_index(feature[0..-4] + DLEXT2)
78
+ x = search_index(feature[0..-4] + DLEXT2)
79
+ return x if x
77
80
  end
78
81
  else
79
82
  # other, unknown extension. For example, `.rake`. Since we haven't
80
83
  # cached these, we legitimately need to run the load path search.
81
- raise LoadPathCache::FallbackScan
84
+ raise(LoadPathCache::FallbackScan, '', [])
82
85
  end
83
86
  end
87
+
88
+ # In development mode, we don't want to confidently return failures for
89
+ # cases where the file doesn't appear to be on the load path. We should
90
+ # be able to detect newly-created files without rebooting the
91
+ # application.
92
+ raise(LoadPathCache::FallbackScan, '', []) if @development_mode
84
93
  end
85
94
 
86
95
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@@ -103,20 +112,12 @@ module Bootsnap
103
112
  @mutex.synchronize { push_paths_locked(*paths) }
104
113
  end
105
114
 
106
- def each_requirable
107
- @mutex.synchronize do
108
- @index.each do |rel, entry|
109
- yield "#{entry}/#{rel}"
110
- end
111
- end
112
- end
113
-
114
115
  def reinitialize(path_obj = @path_obj)
115
116
  @mutex.synchronize do
116
117
  @path_obj = path_obj
117
118
  ChangeObserver.register(self, @path_obj)
118
119
  @index = {}
119
- @dirs = Hash.new(false)
120
+ @dirs = {}
120
121
  @generated_at = now
121
122
  push_paths_locked(*@path_obj)
122
123
  end
@@ -140,10 +141,11 @@ module Bootsnap
140
141
  p = Path.new(path)
141
142
  @has_relative_paths = true if p.relative?
142
143
  next if p.non_directory?
144
+ expanded_path = p.expanded_path
143
145
  entries, dirs = p.entries_and_dirs(@store)
144
146
  # push -> low precedence -> set only if unset
145
- dirs.each { |dir| @dirs[dir] ||= true }
146
- entries.each { |rel| @index[rel] ||= p.expanded_path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
148
+ entries.each { |rel| @index[rel] ||= expanded_path }
147
149
  end
148
150
  end
149
151
  end
@@ -153,14 +155,19 @@ module Bootsnap
153
155
  paths.map(&:to_s).reverse_each do |path|
154
156
  p = Path.new(path)
155
157
  next if p.non_directory?
158
+ expanded_path = p.expanded_path
156
159
  entries, dirs = p.entries_and_dirs(@store)
157
160
  # unshift -> high precedence -> unconditional set
158
- dirs.each { |dir| @dirs[dir] = true }
159
- entries.each { |rel| @index[rel] = p.expanded_path }
161
+ dirs.each { |dir| @dirs[dir] = path }
162
+ entries.each { |rel| @index[rel] = expanded_path }
160
163
  end
161
164
  end
162
165
  end
163
166
 
167
+ def expand_path(feature)
168
+ maybe_append_extension(File.expand_path(feature))
169
+ end
170
+
164
171
  def stale?
165
172
  @development_mode && @generated_at + AGE_THRESHOLD < now
166
173
  end
@@ -173,16 +180,45 @@ module Bootsnap
173
180
  def search_index(f)
174
181
  try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
175
182
  end
183
+
184
+ def maybe_append_extension(f)
185
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
186
+ end
176
187
  else
177
188
  def search_index(f)
178
189
  try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
179
190
  end
191
+
192
+ def maybe_append_extension(f)
193
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
194
+ end
180
195
  end
181
196
 
182
- def try_index(f)
183
- if p = @index[f]
184
- p + '/' + f
197
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
+ if s.respond_to?(:-@)
199
+ if (-s).equal?(s) && (-s.dup).equal?(s)
200
+ def try_index(f)
201
+ if (p = @index[f])
202
+ -(File.join(p, f).freeze)
203
+ end
204
+ end
205
+ else
206
+ def try_index(f)
207
+ if (p = @index[f])
208
+ -File.join(p, f).untaint
209
+ end
210
+ end
185
211
  end
212
+ else
213
+ def try_index(f)
214
+ if (p = @index[f])
215
+ File.join(p, f)
216
+ end
217
+ end
218
+ end
219
+
220
+ def try_ext(f)
221
+ f if File.exist?(f)
186
222
  end
187
223
  end
188
224
  end