bootsnap 1.7.2 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,63 +1,86 @@
1
1
  # frozen_string_literal: true
2
- require('bootsnap/bootsnap')
2
+
3
+ require("bootsnap/bootsnap")
3
4
 
4
5
  module Bootsnap
5
6
  module CompileCache
6
7
  module YAML
7
- class << self
8
- attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
8
+ Uncompilable = Class.new(StandardError)
9
+ UnsupportedTags = Class.new(Uncompilable)
9
10
 
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)
17
- end
11
+ SUPPORTED_INTERNAL_ENCODINGS = [
12
+ nil, # UTF-8
13
+ Encoding::UTF_8,
14
+ Encoding::ASCII,
15
+ Encoding::BINARY,
16
+ ].freeze
18
17
 
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
18
+ class << self
19
+ attr_accessor(:msgpack_factory, :supported_options)
20
+ attr_reader(:implementation, :cache_dir)
25
21
 
26
- def input_to_output(data, kwargs)
27
- ::YAML.load(data, **(kwargs || {}))
22
+ def cache_dir=(cache_dir)
23
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}yaml" : "#{cache_dir}-yaml"
28
24
  end
29
25
 
30
- def precompile(path, cache_dir: YAML.cache_dir)
31
- Bootsnap::CompileCache::Native.precompile(
26
+ def precompile(path)
27
+ return false unless CompileCache::YAML.supported_internal_encoding?
28
+
29
+ CompileCache::Native.precompile(
32
30
  cache_dir,
33
31
  path.to_s,
34
- Bootsnap::CompileCache::YAML,
32
+ @implementation,
35
33
  )
36
34
  end
37
35
 
38
36
  def install!(cache_dir)
39
37
  self.cache_dir = cache_dir
40
38
  init!
41
- ::YAML.singleton_class.prepend(Patch)
39
+ ::YAML.singleton_class.prepend(@implementation::Patch)
40
+ end
41
+
42
+ # Psych coerce strings to `Encoding.default_internal` but Message Pack only support
43
+ # UTF-8, US-ASCII and BINARY. So if Encoding.default_internal is set to anything else
44
+ # we can't safely use the cache
45
+ def supported_internal_encoding?
46
+ SUPPORTED_INTERNAL_ENCODINGS.include?(Encoding.default_internal)
47
+ end
48
+
49
+ module EncodingAwareSymbols
50
+ extend self
51
+
52
+ def unpack(payload)
53
+ (+payload).force_encoding(Encoding::UTF_8).to_sym
54
+ end
42
55
  end
43
56
 
44
57
  def init!
45
- require('yaml')
46
- require('msgpack')
47
- require('date')
58
+ require("yaml")
59
+ require("msgpack")
60
+ require("date")
61
+
62
+ @implementation = ::YAML::VERSION >= "4" ? Psych4 : Psych3
63
+ if @implementation::Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
64
+ @implementation::Patch.send(:remove_method, :unsafe_load_file)
65
+ end
48
66
 
49
67
  # MessagePack serializes symbols as strings by default.
50
68
  # We want them to roundtrip cleanly, so we use a custom factory.
51
69
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
70
  factory = MessagePack::Factory.new
53
- factory.register_type(0x00, Symbol)
71
+ factory.register_type(
72
+ 0x00,
73
+ Symbol,
74
+ packer: :to_msgpack_ext,
75
+ unpacker: EncodingAwareSymbols.method(:unpack).to_proc,
76
+ )
54
77
 
55
78
  if defined? MessagePack::Timestamp
56
79
  factory.register_type(
57
80
  MessagePack::Timestamp::TYPE, # or just -1
58
81
  Time,
59
82
  packer: MessagePack::Time::Packer,
60
- unpacker: MessagePack::Time::Unpacker
83
+ unpacker: MessagePack::Time::Unpacker,
61
84
  )
62
85
 
63
86
  marshal_fallback = {
@@ -77,38 +100,251 @@ module Bootsnap
77
100
  self.supported_options = []
78
101
  params = ::YAML.method(:load).parameters
79
102
  if params.include?([:key, :symbolize_names])
80
- self.supported_options << :symbolize_names
103
+ supported_options << :symbolize_names
81
104
  end
82
105
  if params.include?([:key, :freeze])
83
- if factory.load(factory.dump('yaml'), freeze: true).frozen?
84
- self.supported_options << :freeze
106
+ if factory.load(factory.dump("yaml"), freeze: true).frozen?
107
+ supported_options << :freeze
108
+ end
109
+ end
110
+ supported_options.freeze
111
+ end
112
+
113
+ def patch
114
+ @implementation::Patch
115
+ end
116
+
117
+ def strict_load(payload)
118
+ ast = ::YAML.parse(payload)
119
+ return ast unless ast
120
+
121
+ strict_visitor.create.visit(ast)
122
+ end
123
+
124
+ def strict_visitor
125
+ self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
126
+ def visit(target)
127
+ if target.tag
128
+ raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
129
+ end
130
+
131
+ super
85
132
  end
86
133
  end
87
- self.supported_options.freeze
88
134
  end
89
135
  end
90
136
 
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?
137
+ module Psych4
138
+ extend self
139
+
140
+ def input_to_storage(contents, _)
141
+ obj = SafeLoad.input_to_storage(contents, nil)
142
+ if UNCOMPILABLE.equal?(obj)
143
+ obj = UnsafeLoad.input_to_storage(contents, nil)
144
+ end
145
+ obj
146
+ end
147
+
148
+ module UnsafeLoad
149
+ extend self
150
+
151
+ def input_to_storage(contents, _)
152
+ obj = ::YAML.unsafe_load(contents)
153
+ packer = CompileCache::YAML.msgpack_factory.packer
154
+ packer.pack(false) # not safe loaded
155
+ begin
156
+ packer.pack(obj)
157
+ rescue NoMethodError, RangeError
158
+ return UNCOMPILABLE # The object included things that we can't serialize
159
+ end
160
+ packer.to_s
97
161
  end
98
162
 
163
+ def storage_to_output(data, kwargs)
164
+ if kwargs&.key?(:symbolize_names)
165
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
166
+ end
167
+
168
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
169
+ unpacker.feed(data)
170
+ _safe_loaded = unpacker.unpack
171
+ unpacker.unpack
172
+ end
173
+
174
+ def input_to_output(data, kwargs)
175
+ ::YAML.unsafe_load(data, **(kwargs || {}))
176
+ end
177
+ end
178
+
179
+ module SafeLoad
180
+ extend self
181
+
182
+ def input_to_storage(contents, _)
183
+ obj = begin
184
+ CompileCache::YAML.strict_load(contents)
185
+ rescue Psych::DisallowedClass, Psych::BadAlias, Uncompilable
186
+ return UNCOMPILABLE
187
+ end
188
+
189
+ packer = CompileCache::YAML.msgpack_factory.packer
190
+ packer.pack(true) # safe loaded
191
+ begin
192
+ packer.pack(obj)
193
+ rescue NoMethodError, RangeError
194
+ return UNCOMPILABLE
195
+ end
196
+ packer.to_s
197
+ end
198
+
199
+ def storage_to_output(data, kwargs)
200
+ if kwargs&.key?(:symbolize_names)
201
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
202
+ end
203
+
204
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
205
+ unpacker.feed(data)
206
+ safe_loaded = unpacker.unpack
207
+ if safe_loaded
208
+ unpacker.unpack
209
+ else
210
+ UNCOMPILABLE
211
+ end
212
+ end
213
+
214
+ def input_to_output(data, kwargs)
215
+ ::YAML.load(data, **(kwargs || {}))
216
+ end
217
+ end
218
+
219
+ module Patch
220
+ def load_file(path, *args)
221
+ return super unless CompileCache::YAML.supported_internal_encoding?
222
+
223
+ return super if args.size > 1
224
+
225
+ if (kwargs = args.first)
226
+ return super unless kwargs.is_a?(Hash)
227
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
228
+ end
229
+
230
+ begin
231
+ CompileCache::Native.fetch(
232
+ CompileCache::YAML.cache_dir,
233
+ File.realpath(path),
234
+ CompileCache::YAML::Psych4::SafeLoad,
235
+ kwargs,
236
+ )
237
+ rescue Errno::EACCES
238
+ CompileCache.permission_error(path)
239
+ end
240
+ end
241
+
242
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
243
+
244
+ def unsafe_load_file(path, *args)
245
+ return super unless CompileCache::YAML.supported_internal_encoding?
246
+
247
+ return super if args.size > 1
248
+
249
+ if (kwargs = args.first)
250
+ return super unless kwargs.is_a?(Hash)
251
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
252
+ end
253
+
254
+ begin
255
+ CompileCache::Native.fetch(
256
+ CompileCache::YAML.cache_dir,
257
+ File.realpath(path),
258
+ CompileCache::YAML::Psych4::UnsafeLoad,
259
+ kwargs,
260
+ )
261
+ rescue Errno::EACCES
262
+ CompileCache.permission_error(path)
263
+ end
264
+ end
265
+
266
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
267
+ end
268
+ end
269
+
270
+ module Psych3
271
+ extend self
272
+
273
+ def input_to_storage(contents, _)
274
+ obj = ::YAML.load(contents)
275
+ packer = CompileCache::YAML.msgpack_factory.packer
276
+ packer.pack(false) # not safe loaded
99
277
  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)
278
+ packer.pack(obj)
279
+ rescue NoMethodError, RangeError
280
+ return UNCOMPILABLE # The object included things that we can't serialize
108
281
  end
282
+ packer.to_s
109
283
  end
110
284
 
111
- ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
285
+ def storage_to_output(data, kwargs)
286
+ if kwargs&.key?(:symbolize_names)
287
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
288
+ end
289
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
290
+ unpacker.feed(data)
291
+ _safe_loaded = unpacker.unpack
292
+ unpacker.unpack
293
+ end
294
+
295
+ def input_to_output(data, kwargs)
296
+ ::YAML.load(data, **(kwargs || {}))
297
+ end
298
+
299
+ module Patch
300
+ def load_file(path, *args)
301
+ return super unless CompileCache::YAML.supported_internal_encoding?
302
+
303
+ return super if args.size > 1
304
+
305
+ if (kwargs = args.first)
306
+ return super unless kwargs.is_a?(Hash)
307
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
308
+ end
309
+
310
+ begin
311
+ CompileCache::Native.fetch(
312
+ CompileCache::YAML.cache_dir,
313
+ File.realpath(path),
314
+ CompileCache::YAML::Psych3,
315
+ kwargs,
316
+ )
317
+ rescue Errno::EACCES
318
+ CompileCache.permission_error(path)
319
+ end
320
+ end
321
+
322
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
323
+
324
+ def unsafe_load_file(path, *args)
325
+ return super unless CompileCache::YAML.supported_internal_encoding?
326
+
327
+ return super if args.size > 1
328
+
329
+ if (kwargs = args.first)
330
+ return super unless kwargs.is_a?(Hash)
331
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
332
+ end
333
+
334
+ begin
335
+ CompileCache::Native.fetch(
336
+ CompileCache::YAML.cache_dir,
337
+ File.realpath(path),
338
+ CompileCache::YAML::Psych3,
339
+ kwargs,
340
+ )
341
+ rescue Errno::EACCES
342
+ CompileCache.permission_error(path)
343
+ end
344
+ end
345
+
346
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
347
+ end
112
348
  end
113
349
  end
114
350
  end
@@ -1,13 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  module CompileCache
4
- Error = Class.new(StandardError)
5
+ UNCOMPILABLE = BasicObject.new
6
+ def UNCOMPILABLE.inspect
7
+ "<Bootsnap::UNCOMPILABLE>"
8
+ end
9
+
10
+ Error = Class.new(StandardError)
5
11
  PermissionError = Class.new(Error)
6
12
 
7
- def self.setup(cache_dir:, iseq:, yaml:)
13
+ def self.setup(cache_dir:, iseq:, yaml:, json:)
8
14
  if iseq
9
15
  if supported?
10
- require_relative('compile_cache/iseq')
16
+ require_relative("compile_cache/iseq")
11
17
  Bootsnap::CompileCache::ISeq.install!(cache_dir)
12
18
  elsif $VERBOSE
13
19
  warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
@@ -16,12 +22,21 @@ module Bootsnap
16
22
 
17
23
  if yaml
18
24
  if supported?
19
- require_relative('compile_cache/yaml')
25
+ require_relative("compile_cache/yaml")
20
26
  Bootsnap::CompileCache::YAML.install!(cache_dir)
21
27
  elsif $VERBOSE
22
28
  warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
23
29
  end
24
30
  end
31
+
32
+ if json
33
+ if supported?
34
+ require_relative("compile_cache/json")
35
+ Bootsnap::CompileCache::JSON.install!(cache_dir)
36
+ elsif $VERBOSE
37
+ warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
38
+ end
39
+ end
25
40
  end
26
41
 
27
42
  def self.permission_error(path)
@@ -35,9 +50,7 @@ module Bootsnap
35
50
 
36
51
  def self.supported?
37
52
  # 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")
53
+ RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
41
54
  end
42
55
  end
43
56
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  module ExplicitRequire
4
- ARCHDIR = RbConfig::CONFIG['archdir']
5
- RUBYLIBDIR = RbConfig::CONFIG['rubylibdir']
6
- DLEXT = RbConfig::CONFIG['DLEXT']
5
+ ARCHDIR = RbConfig::CONFIG["archdir"]
6
+ RUBYLIBDIR = RbConfig::CONFIG["rubylibdir"]
7
+ DLEXT = RbConfig::CONFIG["DLEXT"]
7
8
 
8
9
  def self.from_self(feature)
9
10
  require_relative("../#{feature}")
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative('../explicit_require')
3
+ require_relative("../explicit_require")
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -28,15 +28,16 @@ module Bootsnap
28
28
  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
29
29
  # Builtin features are of the form 'enumerator.so'.
30
30
  # All others include paths.
31
- next unless feat.size < 20 && !feat.include?('/')
31
+ next unless feat.size < 20 && !feat.include?("/")
32
32
 
33
- base = File.basename(feat, '.*') # enumerator.so -> enumerator
33
+ base = File.basename(feat, ".*") # enumerator.so -> enumerator
34
34
  ext = File.extname(feat) # .so
35
35
 
36
36
  features[feat] = nil # enumerator.so
37
37
  features[base] = nil # enumerator
38
38
 
39
39
  next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
40
+
40
41
  DL_EXTENSIONS.each do |dl_ext|
41
42
  features["#{base}#{dl_ext}"] = nil # enumerator.bundle
42
43
  end
@@ -44,21 +45,27 @@ module Bootsnap
44
45
 
45
46
  # Try to resolve this feature to an absolute path without traversing the
46
47
  # loadpath.
47
- def find(feature)
48
+ def find(feature, try_extensions: true)
48
49
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
50
  feature = feature.to_s.freeze
50
- return feature if absolute_path?(feature)
51
- return expand_path(feature) if feature.start_with?('./')
51
+
52
+ return feature if Bootsnap.absolute_path?(feature)
53
+
54
+ if feature.start_with?("./", "../")
55
+ return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
56
+ end
57
+
52
58
  @mutex.synchronize do
53
- x = search_index(feature)
59
+ x = search_index(feature, try_extensions: try_extensions)
54
60
  return x if x
61
+ return unless try_extensions
55
62
 
56
63
  # Ruby has some built-in features that require lies about.
57
64
  # For example, 'enumerator' is built in. If you require it, ruby
58
65
  # returns false as if it were already loaded; however, there is no
59
66
  # file to find on disk. We've pre-built a list of these, and we
60
67
  # return false if any of them is loaded.
61
- raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
68
+ return false if BUILTIN_FEATURES.key?(feature)
62
69
 
63
70
  # The feature wasn't found on our preliminary search through the index.
64
71
  # We resolve this differently depending on what the extension was.
@@ -67,13 +74,14 @@ module Bootsnap
67
74
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
75
  # failure and there's nothing more we can do to find the file.
69
76
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS
77
+ when "", *CACHED_EXTENSIONS
71
78
  nil
72
79
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
80
  # is '.bundle'. This is where we handle that case.
74
81
  when DOT_SO
75
82
  x = search_index(feature[0..-4] + DLEXT)
76
83
  return x if x
84
+
77
85
  if DLEXT2
78
86
  x = search_index(feature[0..-4] + DLEXT2)
79
87
  return x if x
@@ -81,7 +89,7 @@ module Bootsnap
81
89
  else
82
90
  # other, unknown extension. For example, `.rake`. Since we haven't
83
91
  # cached these, we legitimately need to run the load path search.
84
- raise(LoadPathCache::FallbackScan, '', [])
92
+ return FALLBACK_SCAN
85
93
  end
86
94
  end
87
95
 
@@ -89,26 +97,18 @@ module Bootsnap
89
97
  # cases where the file doesn't appear to be on the load path. We should
90
98
  # be able to detect newly-created files without rebooting the
91
99
  # application.
92
- raise(LoadPathCache::FallbackScan, '', []) if @development_mode
93
- end
94
-
95
- if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
96
- def absolute_path?(path)
97
- path[1] == ':'
98
- end
99
- else
100
- def absolute_path?(path)
101
- path.start_with?(SLASH)
102
- end
100
+ return FALLBACK_SCAN if @development_mode
103
101
  end
104
102
 
105
103
  def unshift_paths(sender, *paths)
106
104
  return unless sender == @path_obj
105
+
107
106
  @mutex.synchronize { unshift_paths_locked(*paths) }
108
107
  end
109
108
 
110
109
  def push_paths(sender, *paths)
111
110
  return unless sender == @path_obj
111
+
112
112
  @mutex.synchronize { push_paths_locked(*paths) }
113
113
  end
114
114
 
@@ -141,6 +141,9 @@ module Bootsnap
141
141
  p = Path.new(path)
142
142
  @has_relative_paths = true if p.relative?
143
143
  next if p.non_directory?
144
+
145
+ p = p.to_realpath
146
+
144
147
  expanded_path = p.expanded_path
145
148
  entries, dirs = p.entries_and_dirs(@store)
146
149
  # push -> low precedence -> set only if unset
@@ -155,6 +158,9 @@ module Bootsnap
155
158
  paths.map(&:to_s).reverse_each do |path|
156
159
  p = Path.new(path)
157
160
  next if p.non_directory?
161
+
162
+ p = p.to_realpath
163
+
158
164
  expanded_path = p.expanded_path
159
165
  entries, dirs = p.entries_and_dirs(@store)
160
166
  # unshift -> high precedence -> unconditional set
@@ -177,48 +183,62 @@ module Bootsnap
177
183
  end
178
184
 
179
185
  if DLEXT2
180
- def search_index(f)
181
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
186
+ def search_index(feature, try_extensions: true)
187
+ if try_extensions
188
+ try_index(feature + DOT_RB) ||
189
+ try_index(feature + DLEXT) ||
190
+ try_index(feature + DLEXT2) ||
191
+ try_index(feature)
192
+ else
193
+ try_index(feature)
194
+ end
182
195
  end
183
196
 
184
- def maybe_append_extension(f)
185
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
197
+ def maybe_append_extension(feature)
198
+ try_ext(feature + DOT_RB) ||
199
+ try_ext(feature + DLEXT) ||
200
+ try_ext(feature + DLEXT2) ||
201
+ feature
186
202
  end
187
203
  else
188
- def search_index(f)
189
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
204
+ def search_index(feature, try_extensions: true)
205
+ if try_extensions
206
+ try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
207
+ else
208
+ try_index(feature)
209
+ end
190
210
  end
191
211
 
192
- def maybe_append_extension(f)
193
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
212
+ def maybe_append_extension(feature)
213
+ try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
194
214
  end
195
215
  end
196
216
 
197
217
  s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
218
  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)
219
+ if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= "2.7"
220
+ def try_index(feature)
221
+ if (path = @index[feature])
222
+ -File.join(path, feature).freeze
203
223
  end
204
224
  end
205
225
  else
206
- def try_index(f)
207
- if (p = @index[f])
208
- -File.join(p, f).untaint
226
+ def try_index(feature)
227
+ if (path = @index[feature])
228
+ -File.join(path, feature).untaint
209
229
  end
210
230
  end
211
231
  end
212
232
  else
213
- def try_index(f)
214
- if (p = @index[f])
215
- File.join(p, f)
233
+ def try_index(feature)
234
+ if (path = @index[feature])
235
+ File.join(path, feature)
216
236
  end
217
237
  end
218
238
  end
219
239
 
220
- def try_ext(f)
221
- f if File.exist?(f)
240
+ def try_ext(feature)
241
+ feature if File.exist?(feature)
222
242
  end
223
243
  end
224
244
  end