bootsnap 1.6.0 → 1.15.0

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,72 +1,111 @@
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
66
+
67
+ unless const_defined?(:NoTagsVisitor)
68
+ visitor = Class.new(Psych::Visitors::ToRuby) do
69
+ def visit(target)
70
+ if target.tag
71
+ raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
72
+ end
73
+
74
+ super
75
+ end
76
+ end
77
+ const_set(:NoTagsVisitor, visitor)
78
+ end
48
79
 
49
80
  # MessagePack serializes symbols as strings by default.
50
81
  # We want them to roundtrip cleanly, so we use a custom factory.
51
82
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
83
  factory = MessagePack::Factory.new
53
- factory.register_type(0x00, Symbol)
54
84
  factory.register_type(
55
- MessagePack::Timestamp::TYPE, # or just -1
56
- Time,
57
- packer: MessagePack::Time::Packer,
58
- unpacker: MessagePack::Time::Unpacker
85
+ 0x00,
86
+ Symbol,
87
+ packer: :to_msgpack_ext,
88
+ unpacker: EncodingAwareSymbols.method(:unpack).to_proc,
59
89
  )
60
90
 
61
- marshal_fallback = {
62
- packer: ->(value) { Marshal.dump(value) },
63
- unpacker: ->(payload) { Marshal.load(payload) },
64
- }
65
- {
66
- Date => 0x01,
67
- Regexp => 0x02,
68
- }.each do |type, code|
69
- factory.register_type(code, type, marshal_fallback)
91
+ if defined? MessagePack::Timestamp
92
+ factory.register_type(
93
+ MessagePack::Timestamp::TYPE, # or just -1
94
+ Time,
95
+ packer: MessagePack::Time::Packer,
96
+ unpacker: MessagePack::Time::Unpacker,
97
+ )
98
+
99
+ marshal_fallback = {
100
+ packer: ->(value) { Marshal.dump(value) },
101
+ unpacker: ->(payload) { Marshal.load(payload) },
102
+ }
103
+ {
104
+ Date => 0x01,
105
+ Regexp => 0x02,
106
+ }.each do |type, code|
107
+ factory.register_type(code, type, marshal_fallback)
108
+ end
70
109
  end
71
110
 
72
111
  self.msgpack_factory = factory
@@ -74,38 +113,237 @@ module Bootsnap
74
113
  self.supported_options = []
75
114
  params = ::YAML.method(:load).parameters
76
115
  if params.include?([:key, :symbolize_names])
77
- self.supported_options << :symbolize_names
116
+ supported_options << :symbolize_names
78
117
  end
79
- if params.include?([:key, :freeze])
80
- if factory.load(factory.dump('yaml'), freeze: true).frozen?
81
- self.supported_options << :freeze
82
- end
118
+ if params.include?([:key, :freeze]) && factory.load(factory.dump("yaml"), freeze: true).frozen?
119
+ supported_options << :freeze
83
120
  end
84
- self.supported_options.freeze
121
+ supported_options.freeze
122
+ end
123
+
124
+ def patch
125
+ @implementation::Patch
126
+ end
127
+
128
+ def strict_load(payload)
129
+ ast = ::YAML.parse(payload)
130
+ return ast unless ast
131
+
132
+ NoTagsVisitor.create.visit(ast)
85
133
  end
86
134
  end
87
135
 
88
- module Patch
89
- def load_file(path, *args)
90
- return super if args.size > 1
91
- if kwargs = args.first
92
- return super unless kwargs.is_a?(Hash)
93
- return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
136
+ module Psych4
137
+ extend self
138
+
139
+ def input_to_storage(contents, _)
140
+ obj = SafeLoad.input_to_storage(contents, nil)
141
+ if UNCOMPILABLE.equal?(obj)
142
+ obj = UnsafeLoad.input_to_storage(contents, nil)
94
143
  end
144
+ obj
145
+ end
95
146
 
147
+ module UnsafeLoad
148
+ extend self
149
+
150
+ def input_to_storage(contents, _)
151
+ obj = ::YAML.unsafe_load(contents)
152
+ packer = CompileCache::YAML.msgpack_factory.packer
153
+ packer.pack(false) # not safe loaded
154
+ begin
155
+ packer.pack(obj)
156
+ rescue NoMethodError, RangeError
157
+ return UNCOMPILABLE # The object included things that we can't serialize
158
+ end
159
+ packer.to_s
160
+ end
161
+
162
+ def storage_to_output(data, kwargs)
163
+ if kwargs&.key?(:symbolize_names)
164
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
165
+ end
166
+
167
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
168
+ unpacker.feed(data)
169
+ _safe_loaded = unpacker.unpack
170
+ unpacker.unpack
171
+ end
172
+
173
+ def input_to_output(data, kwargs)
174
+ ::YAML.unsafe_load(data, **(kwargs || {}))
175
+ end
176
+ end
177
+
178
+ module SafeLoad
179
+ extend self
180
+
181
+ def input_to_storage(contents, _)
182
+ obj = begin
183
+ CompileCache::YAML.strict_load(contents)
184
+ rescue Psych::DisallowedClass, Psych::BadAlias, Uncompilable
185
+ return UNCOMPILABLE
186
+ end
187
+
188
+ packer = CompileCache::YAML.msgpack_factory.packer
189
+ packer.pack(true) # safe loaded
190
+ begin
191
+ packer.pack(obj)
192
+ rescue NoMethodError, RangeError
193
+ return UNCOMPILABLE
194
+ end
195
+ packer.to_s
196
+ end
197
+
198
+ def storage_to_output(data, kwargs)
199
+ if kwargs&.key?(:symbolize_names)
200
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
201
+ end
202
+
203
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
204
+ unpacker.feed(data)
205
+ safe_loaded = unpacker.unpack
206
+ if safe_loaded
207
+ unpacker.unpack
208
+ else
209
+ UNCOMPILABLE
210
+ end
211
+ end
212
+
213
+ def input_to_output(data, kwargs)
214
+ ::YAML.load(data, **(kwargs || {}))
215
+ end
216
+ end
217
+
218
+ module Patch
219
+ def load_file(path, *args)
220
+ return super unless CompileCache::YAML.supported_internal_encoding?
221
+
222
+ return super if args.size > 1
223
+
224
+ if (kwargs = args.first)
225
+ return super unless kwargs.is_a?(Hash)
226
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
227
+ end
228
+
229
+ begin
230
+ CompileCache::Native.fetch(
231
+ CompileCache::YAML.cache_dir,
232
+ File.realpath(path),
233
+ CompileCache::YAML::Psych4::SafeLoad,
234
+ kwargs,
235
+ )
236
+ rescue Errno::EACCES
237
+ CompileCache.permission_error(path)
238
+ end
239
+ end
240
+
241
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
242
+
243
+ def unsafe_load_file(path, *args)
244
+ return super unless CompileCache::YAML.supported_internal_encoding?
245
+
246
+ return super if args.size > 1
247
+
248
+ if (kwargs = args.first)
249
+ return super unless kwargs.is_a?(Hash)
250
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
251
+ end
252
+
253
+ begin
254
+ CompileCache::Native.fetch(
255
+ CompileCache::YAML.cache_dir,
256
+ File.realpath(path),
257
+ CompileCache::YAML::Psych4::UnsafeLoad,
258
+ kwargs,
259
+ )
260
+ rescue Errno::EACCES
261
+ CompileCache.permission_error(path)
262
+ end
263
+ end
264
+
265
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
266
+ end
267
+ end
268
+
269
+ module Psych3
270
+ extend self
271
+
272
+ def input_to_storage(contents, _)
273
+ obj = ::YAML.load(contents)
274
+ packer = CompileCache::YAML.msgpack_factory.packer
275
+ packer.pack(false) # not safe loaded
96
276
  begin
97
- ::Bootsnap::CompileCache::Native.fetch(
98
- Bootsnap::CompileCache::YAML.cache_dir,
99
- path,
100
- ::Bootsnap::CompileCache::YAML,
101
- kwargs,
102
- )
103
- rescue Errno::EACCES
104
- ::Bootsnap::CompileCache.permission_error(path)
277
+ packer.pack(obj)
278
+ rescue NoMethodError, RangeError
279
+ return UNCOMPILABLE # The object included things that we can't serialize
280
+ end
281
+ packer.to_s
282
+ end
283
+
284
+ def storage_to_output(data, kwargs)
285
+ if kwargs&.key?(:symbolize_names)
286
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
105
287
  end
288
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
289
+ unpacker.feed(data)
290
+ _safe_loaded = unpacker.unpack
291
+ unpacker.unpack
106
292
  end
107
293
 
108
- ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
294
+ def input_to_output(data, kwargs)
295
+ ::YAML.load(data, **(kwargs || {}))
296
+ end
297
+
298
+ module Patch
299
+ def load_file(path, *args)
300
+ return super unless CompileCache::YAML.supported_internal_encoding?
301
+
302
+ return super if args.size > 1
303
+
304
+ if (kwargs = args.first)
305
+ return super unless kwargs.is_a?(Hash)
306
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
307
+ end
308
+
309
+ begin
310
+ CompileCache::Native.fetch(
311
+ CompileCache::YAML.cache_dir,
312
+ File.realpath(path),
313
+ CompileCache::YAML::Psych3,
314
+ kwargs,
315
+ )
316
+ rescue Errno::EACCES
317
+ CompileCache.permission_error(path)
318
+ end
319
+ end
320
+
321
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
322
+
323
+ def unsafe_load_file(path, *args)
324
+ return super unless CompileCache::YAML.supported_internal_encoding?
325
+
326
+ return super if args.size > 1
327
+
328
+ if (kwargs = args.first)
329
+ return super unless kwargs.is_a?(Hash)
330
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
331
+ end
332
+
333
+ begin
334
+ CompileCache::Native.fetch(
335
+ CompileCache::YAML.cache_dir,
336
+ File.realpath(path),
337
+ CompileCache::YAML::Psych3,
338
+ kwargs,
339
+ )
340
+ rescue Errno::EACCES
341
+ CompileCache.permission_error(path)
342
+ end
343
+ end
344
+
345
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
346
+ end
109
347
  end
110
348
  end
111
349
  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:, readonly: false)
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,25 @@ 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
40
+
41
+ if supported? && defined?(Bootsnap::CompileCache::Native)
42
+ Bootsnap::CompileCache::Native.readonly = readonly
43
+ end
25
44
  end
26
45
 
27
46
  def self.permission_error(path)
@@ -35,9 +54,7 @@ module Bootsnap
35
54
 
36
55
  def self.supported?
37
56
  # 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")
57
+ RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
41
58
  end
42
59
  end
43
60
  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}")