bootsnap 1.9.1 → 1.10.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,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,23 @@ 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
55
  self.supported_options = [:freeze]
50
56
  end
51
- self.supported_options.freeze
57
+ supported_options.freeze
52
58
  end
53
59
  end
54
60
 
55
61
  module Patch
56
62
  def load_file(path, *args)
57
63
  return super if args.size > 1
58
- if kwargs = args.first
64
+
65
+ if (kwargs = args.first)
59
66
  return super unless kwargs.is_a?(Hash)
60
67
  return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
61
68
  end
@@ -1,66 +1,42 @@
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)
9
-
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
8
+ UnsupportedTags = Class.new(StandardError)
17
9
 
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
24
-
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
31
- end
10
+ class << self
11
+ attr_accessor(:msgpack_factory, :supported_options)
12
+ attr_reader(:implementation, :cache_dir)
32
13
 
33
- def strict_load(payload, *args)
34
- ast = ::YAML.parse(payload)
35
- return ast unless ast
36
- strict_visitor.create(*args).visit(ast)
14
+ def cache_dir=(cache_dir)
15
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}yaml" : "#{cache_dir}-yaml"
37
16
  end
38
- ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
39
17
 
40
- def precompile(path, cache_dir: YAML.cache_dir)
18
+ def precompile(path)
41
19
  Bootsnap::CompileCache::Native.precompile(
42
20
  cache_dir,
43
21
  path.to_s,
44
- Bootsnap::CompileCache::YAML,
22
+ @implementation,
45
23
  )
46
24
  end
47
25
 
48
26
  def install!(cache_dir)
49
27
  self.cache_dir = cache_dir
50
28
  init!
51
- ::YAML.singleton_class.prepend(Patch)
29
+ ::YAML.singleton_class.prepend(@implementation::Patch)
52
30
  end
53
31
 
54
32
  def init!
55
- require('yaml')
56
- require('msgpack')
57
- require('date')
33
+ require("yaml")
34
+ require("msgpack")
35
+ require("date")
58
36
 
59
- if Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
60
- Patch.send(:remove_method, :unsafe_load_file)
61
- end
62
- if Patch.method_defined?(:load_file) && ::YAML::VERSION >= '4'
63
- Patch.send(:remove_method, :load_file)
37
+ @implementation = ::YAML::VERSION >= "4" ? Psych4 : Psych3
38
+ if @implementation::Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
39
+ @implementation::Patch.send(:remove_method, :unsafe_load_file)
64
40
  end
65
41
 
66
42
  # MessagePack serializes symbols as strings by default.
@@ -74,7 +50,7 @@ module Bootsnap
74
50
  MessagePack::Timestamp::TYPE, # or just -1
75
51
  Time,
76
52
  packer: MessagePack::Time::Packer,
77
- unpacker: MessagePack::Time::Unpacker
53
+ unpacker: MessagePack::Time::Unpacker,
78
54
  )
79
55
 
80
56
  marshal_fallback = {
@@ -94,70 +70,232 @@ module Bootsnap
94
70
  self.supported_options = []
95
71
  params = ::YAML.method(:load).parameters
96
72
  if params.include?([:key, :symbolize_names])
97
- self.supported_options << :symbolize_names
73
+ supported_options << :symbolize_names
98
74
  end
99
75
  if params.include?([:key, :freeze])
100
- if factory.load(factory.dump('yaml'), freeze: true).frozen?
101
- self.supported_options << :freeze
76
+ if factory.load(factory.dump("yaml"), freeze: true).frozen?
77
+ supported_options << :freeze
102
78
  end
103
79
  end
104
- self.supported_options.freeze
80
+ supported_options.freeze
81
+ end
82
+
83
+ def patch
84
+ @implementation::Patch
85
+ end
86
+
87
+ def strict_load(payload)
88
+ ast = ::YAML.parse(payload)
89
+ return ast unless ast
90
+
91
+ strict_visitor.create.visit(ast)
105
92
  end
106
93
 
107
94
  def strict_visitor
108
95
  self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
109
96
  def visit(target)
110
97
  if target.tag
111
- raise Uncompilable, "YAML tags are not supported: #{target.tag}"
98
+ raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
112
99
  end
100
+
113
101
  super
114
102
  end
115
103
  end
116
104
  end
117
105
  end
118
106
 
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?
107
+ module Psych4
108
+ extend self
109
+
110
+ def input_to_storage(contents, _)
111
+ obj = SafeLoad.input_to_storage(contents, nil)
112
+ if UNCOMPILABLE.equal?(obj)
113
+ obj = UnsafeLoad.input_to_storage(contents, nil)
125
114
  end
115
+ obj
116
+ end
126
117
 
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)
118
+ module UnsafeLoad
119
+ extend self
120
+
121
+ def input_to_storage(contents, _)
122
+ obj = CompileCache::YAML.strict_load(contents)
123
+ packer = CompileCache::YAML.msgpack_factory.packer
124
+ packer.pack(false) # not safe loaded
125
+ packer.pack(obj)
126
+ packer.to_s
127
+ rescue NoMethodError, RangeError, UnsupportedTags
128
+ UNCOMPILABLE # The object included things that we can't serialize
129
+ end
130
+
131
+ def storage_to_output(data, kwargs)
132
+ if kwargs&.key?(:symbolize_names)
133
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
134
+ end
135
+
136
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
137
+ unpacker.feed(data)
138
+ _safe_loaded = unpacker.unpack
139
+ unpacker.unpack
140
+ end
141
+
142
+ def input_to_output(data, kwargs)
143
+ ::YAML.unsafe_load(data, **(kwargs || {}))
136
144
  end
137
145
  end
138
146
 
139
- ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
147
+ module SafeLoad
148
+ extend self
140
149
 
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?
150
+ def input_to_storage(contents, _)
151
+ obj = ::YAML.load(contents)
152
+ packer = CompileCache::YAML.msgpack_factory.packer
153
+ packer.pack(true) # safe loaded
154
+ packer.pack(obj)
155
+ packer.to_s
156
+ rescue NoMethodError, RangeError, Psych::DisallowedClass, Psych::BadAlias
157
+ UNCOMPILABLE # The object included things that we can't serialize
146
158
  end
147
159
 
148
- 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)
160
+ def storage_to_output(data, kwargs)
161
+ if kwargs&.key?(:symbolize_names)
162
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
163
+ end
164
+
165
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
166
+ unpacker.feed(data)
167
+ safe_loaded = unpacker.unpack
168
+ if safe_loaded
169
+ unpacker.unpack
170
+ else
171
+ UNCOMPILABLE
172
+ end
173
+ end
174
+
175
+ def input_to_output(data, kwargs)
176
+ ::YAML.load(data, **(kwargs || {}))
177
+ end
178
+ end
179
+
180
+ module Patch
181
+ def load_file(path, *args)
182
+ return super if args.size > 1
183
+
184
+ if (kwargs = args.first)
185
+ return super unless kwargs.is_a?(Hash)
186
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
187
+ end
188
+
189
+ begin
190
+ ::Bootsnap::CompileCache::Native.fetch(
191
+ Bootsnap::CompileCache::YAML.cache_dir,
192
+ File.realpath(path),
193
+ ::Bootsnap::CompileCache::YAML::Psych4::SafeLoad,
194
+ kwargs,
195
+ )
196
+ rescue Errno::EACCES
197
+ ::Bootsnap::CompileCache.permission_error(path)
198
+ end
199
+ end
200
+
201
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
202
+
203
+ def unsafe_load_file(path, *args)
204
+ return super if args.size > 1
205
+
206
+ if (kwargs = args.first)
207
+ return super unless kwargs.is_a?(Hash)
208
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
209
+ end
210
+
211
+ begin
212
+ ::Bootsnap::CompileCache::Native.fetch(
213
+ Bootsnap::CompileCache::YAML.cache_dir,
214
+ File.realpath(path),
215
+ ::Bootsnap::CompileCache::YAML::Psych4::UnsafeLoad,
216
+ kwargs,
217
+ )
218
+ rescue Errno::EACCES
219
+ ::Bootsnap::CompileCache.permission_error(path)
220
+ end
221
+ end
222
+
223
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
224
+ end
225
+ end
226
+
227
+ module Psych3
228
+ extend self
229
+
230
+ def input_to_storage(contents, _)
231
+ obj = CompileCache::YAML.strict_load(contents)
232
+ packer = CompileCache::YAML.msgpack_factory.packer
233
+ packer.pack(false) # not safe loaded
234
+ packer.pack(obj)
235
+ packer.to_s
236
+ rescue NoMethodError, RangeError, UnsupportedTags
237
+ UNCOMPILABLE # The object included things that we can't serialize
238
+ end
239
+
240
+ def storage_to_output(data, kwargs)
241
+ if kwargs&.key?(:symbolize_names)
242
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
157
243
  end
244
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
245
+ unpacker.feed(data)
246
+ _safe_loaded = unpacker.unpack
247
+ unpacker.unpack
158
248
  end
159
249
 
160
- ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
250
+ def input_to_output(data, kwargs)
251
+ ::YAML.load(data, **(kwargs || {}))
252
+ end
253
+
254
+ module Patch
255
+ def load_file(path, *args)
256
+ return super if args.size > 1
257
+
258
+ if (kwargs = args.first)
259
+ return super unless kwargs.is_a?(Hash)
260
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
261
+ end
262
+
263
+ begin
264
+ ::Bootsnap::CompileCache::Native.fetch(
265
+ Bootsnap::CompileCache::YAML.cache_dir,
266
+ File.realpath(path),
267
+ ::Bootsnap::CompileCache::YAML::Psych3,
268
+ kwargs,
269
+ )
270
+ rescue Errno::EACCES
271
+ ::Bootsnap::CompileCache.permission_error(path)
272
+ end
273
+ end
274
+
275
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
276
+
277
+ def unsafe_load_file(path, *args)
278
+ return super if args.size > 1
279
+
280
+ if (kwargs = args.first)
281
+ return super unless kwargs.is_a?(Hash)
282
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
283
+ end
284
+
285
+ begin
286
+ ::Bootsnap::CompileCache::Native.fetch(
287
+ Bootsnap::CompileCache::YAML.cache_dir,
288
+ File.realpath(path),
289
+ ::Bootsnap::CompileCache::YAML::Psych3,
290
+ kwargs,
291
+ )
292
+ rescue Errno::EACCES
293
+ ::Bootsnap::CompileCache.permission_error(path)
294
+ end
295
+ end
296
+
297
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
298
+ end
161
299
  end
162
300
  end
163
301
  end
@@ -1,13 +1,16 @@
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
+
7
+ Error = Class.new(StandardError)
5
8
  PermissionError = Class.new(Error)
6
9
 
7
10
  def self.setup(cache_dir:, iseq:, yaml:, json:)
8
11
  if iseq
9
12
  if supported?
10
- require_relative('compile_cache/iseq')
13
+ require_relative("compile_cache/iseq")
11
14
  Bootsnap::CompileCache::ISeq.install!(cache_dir)
12
15
  elsif $VERBOSE
13
16
  warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
@@ -16,7 +19,7 @@ module Bootsnap
16
19
 
17
20
  if yaml
18
21
  if supported?
19
- require_relative('compile_cache/yaml')
22
+ require_relative("compile_cache/yaml")
20
23
  Bootsnap::CompileCache::YAML.install!(cache_dir)
21
24
  elsif $VERBOSE
22
25
  warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
@@ -25,7 +28,7 @@ module Bootsnap
25
28
 
26
29
  if json
27
30
  if supported?
28
- require_relative('compile_cache/json')
31
+ require_relative("compile_cache/json")
29
32
  Bootsnap::CompileCache::JSON.install!(cache_dir)
30
33
  elsif $VERBOSE
31
34
  warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
@@ -44,9 +47,9 @@ module Bootsnap
44
47
 
45
48
  def self.supported?
46
49
  # 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")
50
+ RUBY_ENGINE == "ruby" &&
51
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
52
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
50
53
  end
51
54
  end
52
55
  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}")