bootsnap 1.8.1 → 1.10.2

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,24 +1,54 @@
1
1
  # frozen_string_literal: true
2
- require('bootsnap/bootsnap')
3
- require('zlib')
2
+
3
+ require("bootsnap/bootsnap")
4
+ require("zlib")
4
5
 
5
6
  module Bootsnap
6
7
  module CompileCache
7
8
  module ISeq
8
9
  class << self
9
- attr_accessor(:cache_dir)
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
14
+ end
15
+ end
16
+
17
+ has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
18
+ if defined? RubyVM::InstructionSequence
19
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
20
+ end
21
+ false
22
+ rescue TypeError
23
+ true
10
24
  end
11
25
 
12
- def self.input_to_storage(_, path)
13
- RubyVM::InstructionSequence.compile_file(path).to_binary
14
- rescue SyntaxError
15
- raise(Uncompilable, 'syntax error')
26
+ if has_ruby_bug_18250
27
+ def self.input_to_storage(_, path)
28
+ iseq = begin
29
+ RubyVM::InstructionSequence.compile_file(path)
30
+ rescue SyntaxError
31
+ return UNCOMPILABLE # syntax error
32
+ end
33
+
34
+ begin
35
+ iseq.to_binary
36
+ rescue TypeError
37
+ return UNCOMPILABLE # ruby bug #18250
38
+ end
39
+ end
40
+ else
41
+ def self.input_to_storage(_, path)
42
+ RubyVM::InstructionSequence.compile_file(path).to_binary
43
+ rescue SyntaxError
44
+ return UNCOMPILABLE # syntax error
45
+ end
16
46
  end
17
47
 
18
48
  def self.storage_to_output(binary, _args)
19
49
  RubyVM::InstructionSequence.load_from_binary(binary)
20
- rescue RuntimeError => e
21
- if e.message == 'broken binary format'
50
+ rescue RuntimeError => error
51
+ if error.message == "broken binary format"
22
52
  STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
23
53
  nil
24
54
  else
@@ -35,7 +65,7 @@ module Bootsnap
35
65
  )
36
66
  end
37
67
 
38
- def self.precompile(path, cache_dir: ISeq.cache_dir)
68
+ def self.precompile(path)
39
69
  Bootsnap::CompileCache::Native.precompile(
40
70
  cache_dir,
41
71
  path.to_s,
@@ -55,8 +85,8 @@ module Bootsnap
55
85
  Bootsnap::CompileCache::ISeq.fetch(path.to_s)
56
86
  rescue Errno::EACCES
57
87
  Bootsnap::CompileCache.permission_error(path)
58
- rescue RuntimeError => e
59
- if e.message =~ /unmatched platform/
88
+ rescue RuntimeError => error
89
+ if error.message =~ /unmatched platform/
60
90
  puts("unmatched platform for file #{path}")
61
91
  end
62
92
  raise
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("bootsnap/bootsnap")
4
+
5
+ module Bootsnap
6
+ module CompileCache
7
+ module JSON
8
+ class << self
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
15
+
16
+ def input_to_storage(payload, _)
17
+ obj = ::JSON.parse(payload)
18
+ msgpack_factory.dump(obj)
19
+ end
20
+
21
+ def storage_to_output(data, kwargs)
22
+ if kwargs&.key?(:symbolize_names)
23
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
24
+ end
25
+ msgpack_factory.load(data, kwargs)
26
+ end
27
+
28
+ def input_to_output(data, kwargs)
29
+ ::JSON.parse(data, **(kwargs || {}))
30
+ end
31
+
32
+ def precompile(path)
33
+ Bootsnap::CompileCache::Native.precompile(
34
+ cache_dir,
35
+ path.to_s,
36
+ self,
37
+ )
38
+ end
39
+
40
+ def install!(cache_dir)
41
+ self.cache_dir = cache_dir
42
+ init!
43
+ if ::JSON.respond_to?(:load_file)
44
+ ::JSON.singleton_class.prepend(Patch)
45
+ end
46
+ end
47
+
48
+ def init!
49
+ require("json")
50
+ require("msgpack")
51
+
52
+ self.msgpack_factory = MessagePack::Factory.new
53
+ self.supported_options = [:symbolize_names]
54
+ if ::JSON.parse('["foo"]', freeze: true).first.frozen?
55
+ if MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
56
+ self.supported_options = [:freeze]
57
+ end
58
+ end
59
+ supported_options.freeze
60
+ end
61
+ end
62
+
63
+ module Patch
64
+ def load_file(path, *args)
65
+ return super if args.size > 1
66
+
67
+ if (kwargs = args.first)
68
+ return super unless kwargs.is_a?(Hash)
69
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
70
+ end
71
+
72
+ begin
73
+ ::Bootsnap::CompileCache::Native.fetch(
74
+ Bootsnap::CompileCache::JSON.cache_dir,
75
+ File.realpath(path),
76
+ ::Bootsnap::CompileCache::JSON,
77
+ kwargs,
78
+ )
79
+ rescue Errno::EACCES
80
+ ::Bootsnap::CompileCache.permission_error(path)
81
+ end
82
+ end
83
+
84
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
85
+ end
86
+ end
87
+ end
88
+ 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
- def self.setup(cache_dir:, iseq:, yaml:)
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,12 +19,21 @@ 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")
23
26
  end
24
27
  end
28
+
29
+ if json
30
+ if supported?
31
+ require_relative("compile_cache/json")
32
+ Bootsnap::CompileCache::JSON.install!(cache_dir)
33
+ elsif $VERBOSE
34
+ warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
35
+ end
36
+ end
25
37
  end
26
38
 
27
39
  def self.permission_error(path)
@@ -35,9 +47,9 @@ module Bootsnap
35
47
 
36
48
  def self.supported?
37
49
  # 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")
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")
41
53
  end
42
54
  end
43
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}")