bootsnap 1.9.1 → 1.10.0

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,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}")