bootsnap 1.9.4 → 1.10.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,17 @@
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 JSON
7
8
  class << self
8
- attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
9
+ attr_accessor(:msgpack_factory, :supported_options)
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}json" : "#{cache_dir}-json"
14
+ end
9
15
 
10
16
  def input_to_storage(payload, _)
11
17
  obj = ::JSON.parse(payload)
@@ -13,7 +19,7 @@ module Bootsnap
13
19
  end
14
20
 
15
21
  def storage_to_output(data, kwargs)
16
- if kwargs && kwargs.key?(:symbolize_names)
22
+ if kwargs&.key?(:symbolize_names)
17
23
  kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
18
24
  end
19
25
  msgpack_factory.load(data, kwargs)
@@ -23,7 +29,7 @@ module Bootsnap
23
29
  ::JSON.parse(data, **(kwargs || {}))
24
30
  end
25
31
 
26
- def precompile(path, cache_dir: self.cache_dir)
32
+ def precompile(path)
27
33
  Bootsnap::CompileCache::Native.precompile(
28
34
  cache_dir,
29
35
  path.to_s,
@@ -40,22 +46,25 @@ module Bootsnap
40
46
  end
41
47
 
42
48
  def init!
43
- require('json')
44
- require('msgpack')
49
+ require("json")
50
+ require("msgpack")
45
51
 
46
52
  self.msgpack_factory = MessagePack::Factory.new
47
53
  self.supported_options = [:symbolize_names]
48
54
  if ::JSON.parse('["foo"]', freeze: true).first.frozen?
49
- self.supported_options = [:freeze]
55
+ if MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
56
+ self.supported_options = [:freeze]
57
+ end
50
58
  end
51
- self.supported_options.freeze
59
+ supported_options.freeze
52
60
  end
53
61
  end
54
62
 
55
63
  module Patch
56
64
  def load_file(path, *args)
57
65
  return super if args.size > 1
58
- if kwargs = args.first
66
+
67
+ if (kwargs = args.first)
59
68
  return super unless kwargs.is_a?(Hash)
60
69
  return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
61
70
  end
@@ -1,80 +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
- obj = strict_load(contents)
12
- msgpack_factory.dump(obj)
13
- rescue NoMethodError, RangeError
14
- # The object included things that we can't serialize
15
- raise(Uncompilable)
16
- end
11
+ SUPPORTED_INTERNAL_ENCODINGS = [
12
+ nil, # UTF-8
13
+ Encoding::UTF_8,
14
+ Encoding::ASCII,
15
+ Encoding::BINARY,
16
+ ].freeze
17
17
 
18
- def storage_to_output(data, kwargs)
19
- if kwargs && kwargs.key?(:symbolize_names)
20
- kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
21
- end
22
- msgpack_factory.load(data, kwargs)
23
- end
18
+ class << self
19
+ attr_accessor(:msgpack_factory, :supported_options)
20
+ attr_reader(:implementation, :cache_dir)
24
21
 
25
- def input_to_output(data, kwargs)
26
- if ::YAML.respond_to?(:unsafe_load)
27
- ::YAML.unsafe_load(data, **(kwargs || {}))
28
- else
29
- ::YAML.load(data, **(kwargs || {}))
30
- end
22
+ def cache_dir=(cache_dir)
23
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}yaml" : "#{cache_dir}-yaml"
31
24
  end
32
25
 
33
- def strict_load(payload, *args)
34
- ast = ::YAML.parse(payload)
35
- return ast unless ast
36
- strict_visitor.create(*args).visit(ast)
37
- end
38
- ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
26
+ def precompile(path)
27
+ return false unless CompileCache::YAML.supported_internal_encoding?
39
28
 
40
- def precompile(path, cache_dir: YAML.cache_dir)
41
- Bootsnap::CompileCache::Native.precompile(
29
+ CompileCache::Native.precompile(
42
30
  cache_dir,
43
31
  path.to_s,
44
- Bootsnap::CompileCache::YAML,
32
+ @implementation,
45
33
  )
46
34
  end
47
35
 
48
36
  def install!(cache_dir)
49
37
  self.cache_dir = cache_dir
50
38
  init!
51
- ::YAML.singleton_class.prepend(Patch)
39
+ ::YAML.singleton_class.prepend(@implementation::Patch)
52
40
  end
53
41
 
54
- def init!
55
- require('yaml')
56
- require('msgpack')
57
- require('date')
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
58
51
 
59
- if Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
60
- Patch.send(:remove_method, :unsafe_load_file)
52
+ def unpack(payload)
53
+ (+payload).force_encoding(Encoding::UTF_8).to_sym
61
54
  end
62
- if Patch.method_defined?(:load_file) && ::YAML::VERSION >= '4'
63
- Patch.send(:remove_method, :load_file)
55
+ end
56
+
57
+ def init!
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)
64
65
  end
65
66
 
66
67
  # MessagePack serializes symbols as strings by default.
67
68
  # We want them to roundtrip cleanly, so we use a custom factory.
68
69
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
69
70
  factory = MessagePack::Factory.new
70
- 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
+ )
71
77
 
72
78
  if defined? MessagePack::Timestamp
73
79
  factory.register_type(
74
80
  MessagePack::Timestamp::TYPE, # or just -1
75
81
  Time,
76
82
  packer: MessagePack::Time::Packer,
77
- unpacker: MessagePack::Time::Unpacker
83
+ unpacker: MessagePack::Time::Unpacker,
78
84
  )
79
85
 
80
86
  marshal_fallback = {
@@ -94,70 +100,251 @@ module Bootsnap
94
100
  self.supported_options = []
95
101
  params = ::YAML.method(:load).parameters
96
102
  if params.include?([:key, :symbolize_names])
97
- self.supported_options << :symbolize_names
103
+ supported_options << :symbolize_names
98
104
  end
99
105
  if params.include?([:key, :freeze])
100
- if factory.load(factory.dump('yaml'), freeze: true).frozen?
101
- self.supported_options << :freeze
106
+ if factory.load(factory.dump("yaml"), freeze: true).frozen?
107
+ supported_options << :freeze
102
108
  end
103
109
  end
104
- self.supported_options.freeze
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)
105
122
  end
106
123
 
107
124
  def strict_visitor
108
125
  self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
109
126
  def visit(target)
110
127
  if target.tag
111
- raise Uncompilable, "YAML tags are not supported: #{target.tag}"
128
+ raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
112
129
  end
130
+
113
131
  super
114
132
  end
115
133
  end
116
134
  end
117
135
  end
118
136
 
119
- module Patch
120
- def load_file(path, *args)
121
- return super if args.size > 1
122
- if kwargs = args.first
123
- return super unless kwargs.is_a?(Hash)
124
- 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)
125
144
  end
145
+ obj
146
+ end
126
147
 
127
- begin
128
- ::Bootsnap::CompileCache::Native.fetch(
129
- Bootsnap::CompileCache::YAML.cache_dir,
130
- File.realpath(path),
131
- ::Bootsnap::CompileCache::YAML,
132
- kwargs,
133
- )
134
- rescue Errno::EACCES
135
- ::Bootsnap::CompileCache.permission_error(path)
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
161
+ end
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 || {}))
136
176
  end
137
177
  end
138
178
 
139
- ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
179
+ module SafeLoad
180
+ extend self
140
181
 
141
- def unsafe_load_file(path, *args)
142
- return super if args.size > 1
143
- if kwargs = args.first
144
- return super unless kwargs.is_a?(Hash)
145
- return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
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
146
197
  end
147
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
148
277
  begin
149
- ::Bootsnap::CompileCache::Native.fetch(
150
- Bootsnap::CompileCache::YAML.cache_dir,
151
- File.realpath(path),
152
- ::Bootsnap::CompileCache::YAML,
153
- kwargs,
154
- )
155
- rescue Errno::EACCES
156
- ::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
157
281
  end
282
+ packer.to_s
158
283
  end
159
284
 
160
- ruby2_keywords :unsafe_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
161
348
  end
162
349
  end
163
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
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,7 +22,7 @@ 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")
@@ -25,7 +31,7 @@ module Bootsnap
25
31
 
26
32
  if json
27
33
  if supported?
28
- require_relative('compile_cache/json')
34
+ require_relative("compile_cache/json")
29
35
  Bootsnap::CompileCache::JSON.install!(cache_dir)
30
36
  elsif $VERBOSE
31
37
  warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
@@ -44,9 +50,9 @@ module Bootsnap
44
50
 
45
51
  def self.supported?
46
52
  # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
47
- RUBY_ENGINE == 'ruby' &&
48
- RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
49
- Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
53
+ RUBY_ENGINE == "ruby" &&
54
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
55
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
50
56
  end
51
57
  end
52
58
  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}")