1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
# frozen_string_literal: true
require_relative "elements"
module Gem
module SafeMarshal
class Reader
class Error < StandardError
end
class UnsupportedVersionError < Error
end
class UnconsumedBytesError < Error
end
class NotImplementedError < Error
end
class EOFError < Error
end
def initialize(io)
@io = io
end
def read!
read_header
root = read_element
raise UnconsumedBytesError unless @io.eof?
root
end
private
MARSHAL_VERSION = [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION].map(&:chr).join.freeze
private_constant :MARSHAL_VERSION
def read_header
v = @io.read(2)
raise UnsupportedVersionError, "Unsupported marshal version #{v.bytes.map(&:ord).join(".")}, expected #{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" unless v == MARSHAL_VERSION
end
def read_byte
@io.getbyte
end
def read_integer
b = read_byte
case b
when 0x00
0
when 0x01
read_byte
when 0x02
read_byte | (read_byte << 8)
when 0x03
read_byte | (read_byte << 8) | (read_byte << 16)
when 0x04
read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24)
when 0xFC
read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) | -0x100000000
when 0xFD
read_byte | (read_byte << 8) | (read_byte << 16) | -0x1000000
when 0xFE
read_byte | (read_byte << 8) | -0x10000
when 0xFF
read_byte | -0x100
when nil
raise EOFError, "Unexpected EOF"
else
signed = (b ^ 128) - 128
if b >= 128
signed + 5
else
signed - 5
end
end
end
def read_element
type = read_byte
case type
when 34 then read_string # ?"
when 48 then read_nil # ?0
when 58 then read_symbol # ?:
when 59 then read_symbol_link # ?;
when 64 then read_object_link # ?@
when 70 then read_false # ?F
when 73 then read_object_with_ivars # ?I
when 84 then read_true # ?T
when 85 then read_user_marshal # ?U
when 91 then read_array # ?[
when 102 then read_float # ?f
when 105 then Elements::Integer.new(read_integer) # ?i
when 108 then read_bignum # ?l
when 111 then read_object # ?o
when 117 then read_user_defined # ?u
when 123 then read_hash # ?{
when 125 then read_hash_with_default_value # ?}
when 101 then read_extended_object # ?e
when 99 then read_class # ?c
when 109 then read_module # ?m
when 77 then read_class_or_module # ?M
when 100 then read_data # ?d
when 47 then read_regexp # ?/
when 83 then read_struct # ?S
when 67 then read_user_class # ?C
when nil
raise EOFError, "Unexpected EOF"
else
raise Error, "Unknown marshal type discriminator #{type.chr.inspect} (#{type})"
end
end
STRING_E_SYMBOL = Elements::Symbol.new("E").freeze
private_constant :STRING_E_SYMBOL
def read_symbol
len = read_integer
if len == 1
byte = read_byte
if byte == 69 # ?E
STRING_E_SYMBOL
else
Elements::Symbol.new(byte.chr)
end
else
name = -@io.read(len)
Elements::Symbol.new(name)
end
end
EMPTY_STRING = Elements::String.new("".b.freeze).freeze
private_constant :EMPTY_STRING
def read_string
length = read_integer
return EMPTY_STRING if length == 0
str = @io.read(length)
Elements::String.new(str)
end
def read_true
Elements::True::TRUE
end
def read_false
Elements::False::FALSE
end
def read_user_defined
name = read_element
binary_string = @io.read(read_integer)
Elements::UserDefined.new(name, binary_string)
end
EMPTY_ARRAY = Elements::Array.new([].freeze).freeze
private_constant :EMPTY_ARRAY
def read_array
length = read_integer
return EMPTY_ARRAY if length == 0
elements = Array.new(length) do
read_element
end
Elements::Array.new(elements)
end
def read_object_with_ivars
object = read_element
ivars = Array.new(read_integer) do
[read_element, read_element]
end
Elements::WithIvars.new(object, ivars)
end
def read_symbol_link
offset = read_integer
Elements::SymbolLink.new(offset)
end
def read_user_marshal
name = read_element
data = read_element
Elements::UserMarshal.new(name, data)
end
# profiling bundle install --full-index shows that
# offset 6 is by far the most common object link,
# so we special case it to avoid allocating a new
# object a third of the time.
# the following are all the object links that
# appear more than 10000 times in my profiling
OBJECT_LINKS = {
6 => Elements::ObjectLink.new(6).freeze,
30 => Elements::ObjectLink.new(30).freeze,
81 => Elements::ObjectLink.new(81).freeze,
34 => Elements::ObjectLink.new(34).freeze,
38 => Elements::ObjectLink.new(38).freeze,
50 => Elements::ObjectLink.new(50).freeze,
91 => Elements::ObjectLink.new(91).freeze,
42 => Elements::ObjectLink.new(42).freeze,
46 => Elements::ObjectLink.new(46).freeze,
150 => Elements::ObjectLink.new(150).freeze,
100 => Elements::ObjectLink.new(100).freeze,
104 => Elements::ObjectLink.new(104).freeze,
108 => Elements::ObjectLink.new(108).freeze,
242 => Elements::ObjectLink.new(242).freeze,
246 => Elements::ObjectLink.new(246).freeze,
139 => Elements::ObjectLink.new(139).freeze,
143 => Elements::ObjectLink.new(143).freeze,
114 => Elements::ObjectLink.new(114).freeze,
308 => Elements::ObjectLink.new(308).freeze,
200 => Elements::ObjectLink.new(200).freeze,
54 => Elements::ObjectLink.new(54).freeze,
62 => Elements::ObjectLink.new(62).freeze,
1_286_245 => Elements::ObjectLink.new(1_286_245).freeze,
}.freeze
private_constant :OBJECT_LINKS
def read_object_link
offset = read_integer
OBJECT_LINKS[offset] || Elements::ObjectLink.new(offset)
end
EMPTY_HASH = Elements::Hash.new([].freeze).freeze
private_constant :EMPTY_HASH
def read_hash
length = read_integer
return EMPTY_HASH if length == 0
pairs = Array.new(length) do
[read_element, read_element]
end
Elements::Hash.new(pairs)
end
def read_hash_with_default_value
pairs = Array.new(read_integer) do
[read_element, read_element]
end
default = read_element
Elements::HashWithDefaultValue.new(pairs, default)
end
def read_object
name = read_element
object = Elements::Object.new(name)
ivars = Array.new(read_integer) do
[read_element, read_element]
end
Elements::WithIvars.new(object, ivars)
end
def read_nil
Elements::Nil::NIL
end
def read_float
string = @io.read(read_integer)
Elements::Float.new(string)
end
def read_bignum
sign = read_byte
data = @io.read(read_integer * 2)
Elements::Bignum.new(sign, data)
end
def read_extended_object
raise NotImplementedError, "Reading Marshal objects of type extended_object is not implemented"
end
def read_class
raise NotImplementedError, "Reading Marshal objects of type class is not implemented"
end
def read_module
raise NotImplementedError, "Reading Marshal objects of type module is not implemented"
end
def read_class_or_module
raise NotImplementedError, "Reading Marshal objects of type class_or_module is not implemented"
end
def read_data
raise NotImplementedError, "Reading Marshal objects of type data is not implemented"
end
def read_regexp
raise NotImplementedError, "Reading Marshal objects of type regexp is not implemented"
end
def read_struct
raise NotImplementedError, "Reading Marshal objects of type struct is not implemented"
end
def read_user_class
name = read_element
wrapped_object = read_element
Elements::UserClass.new(name, wrapped_object)
end
end
end
end
|