Class: Marshal::ReadBuffer

Inherits:
Object show all
Defined in:
opal/opal/corelib/marshal/read_buffer.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ ReadBuffer

Returns a new instance of ReadBuffer.


23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 23

def initialize(input)
  @buffer = `stringToBytes(#{input.to_s})`
  @index = 0
  major = read_byte
  minor = read_byte
  if major != MAJOR_VERSION || minor != MINOR_VERSION
    raise TypeError, "incompatible marshal file format (can't be read)"
  end
  @version = "#{major}.#{minor}"
  @object_cache = []
  @symbols_cache = []
  @ivars = []
end

Instance Attribute Details

#bufferObject (readonly)

Returns the value of attribute buffer.


21
22
23
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 21

def buffer
  @buffer
end

#indexObject (readonly)

Returns the value of attribute index.


21
22
23
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 21

def index
  @index
end

#object_cacheObject (readonly)

Returns the value of attribute object_cache.


21
22
23
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 21

def object_cache
  @object_cache
end

#symbols_cacheObject (readonly)

Returns the value of attribute symbols_cache.


21
22
23
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 21

def symbols_cache
  @symbols_cache
end

#versionObject (readonly)

Returns the value of attribute version.


21
22
23
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 21

def version
  @version
end

Instance Method Details

#lengthObject


37
38
39
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 37

def length
  @buffer.length
end

#read(cache: true) ⇒ Object


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
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 41

def read(cache: true)
  code = read_char
  # The first character indicates the type of the object
  case code
  when '0'
    nil
  when 'T'
    true
  when 'F'
    false
  when 'i'
    read_fixnum
  when 'f'
    read_float
  when 'l'
    read_bignum
  when '"'
    read_string
  when ':'
    read_symbol
  when ';'
    read_cached_symbol
  when '['
    read_array
  when '{'
    read_hash
  when '}'
    read_hashdef
  when '/'
    read_regexp
  when 'S'
    read_struct
  when 'c'
    read_class
  when 'm'
    read_module
  when 'o'
    read_object
  when '@'
    read_cached_object
  when 'e'
    read_extended_object
  when 'I'
    read_primitive_with_ivars
  when 'C'
    read_user_class
  when 'u'
    read_user_defined
  when 'U'
    read_user_marshal
  when 'M'
    raise NotImplementedError, 'ModuleOld type cannot be demarshaled yet' # read_module_old
  when 'd'
    raise NotImplementedError, 'Data type cannot be demarshaled'
  else
    raise ArgumentError, 'dump format error'
  end
end

#read_arrayObject

Reads and returns an array from an input stream

is encoded as '[', 3, 100, 200, 300

Examples:

[100, 200, 300]

243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 243

def read_array
  result = []
  @object_cache << result
  length = read_fixnum
  %x{
    if (length > 0) {
      while (result.length < length) {
        result.push(#{read});
      }
    }

    return result;
  }
end

#read_bignumObject

Reads and returns Bignum from an input stream


172
173
174
175
176
177
178
179
180
181
182
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 172

def read_bignum
  sign = read_char == '-' ? -1 : 1
  size = read_fixnum * 2
  result = 0
  (0...size).each do |exp|
    result += read_char.ord * 2**(exp * 8)
  end
  result = result.to_i * sign
  @object_cache << result
  result
end

#read_byteObject


100
101
102
103
104
105
106
107
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 100

def read_byte
  if @index >= length
    raise ArgumentError, 'marshal data too short'
  end
  result = @buffer[@index]
  @index += 1
  result
end

#read_cached_objectObject

Reads an object that was cached previously by its link

is encoded as [obj1, @1, obj2, @2, obj3, @3]

NOTE: array itself is cached as @0, that's why obj1 is cached a @1, obj2 is @2, etc.

Examples:

obj1 = Object.new
obj2 = Object.new
obj3 = Object.new
[obj1, obj1, obj2, obj2, obj3, obj3]

421
422
423
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 421

def read_cached_object
  object_cache[read_fixnum]
end

#read_cached_symbolObject

Reads a symbol that was previously cache by its link

Is encoded as '[', 6, :a, @0, :b, @1, :c, @2

Examples:

[:a, :a, :b, :b, :c, :c]

232
233
234
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 232

def read_cached_symbol
  symbols_cache[read_fixnum]
end

#read_charObject


109
110
111
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 109

def read_char
  `String.fromCharCode(#{read_byte})`
end

#read_classObject

Reads and returns a Class from an input stream

is encoded as 'c', 'String'

Examples:

String

345
346
347
348
349
350
351
352
353
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 345

def read_class
  klass_name = read_string(cache: false)
  result = safe_const_get(klass_name)
  unless result.class == Class
    raise ArgumentError, "#{klass_name} does not refer to a Class"
  end
  @object_cache << result
  result
end

#read_extended_objectObject

Reads an object that was dynamically extended before marshaling like

is encoded as 'e', :M2, :M1, obj

Examples:

M1 = Module.new
M2 = Module.new
obj = Object.new
obj.extend(M1)
obj.extend(M2)
obj

437
438
439
440
441
442
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 437

def read_extended_object
  mod = safe_const_get(read)
  object = read
  object.extend(mod)
  object
end

#read_fixnumObject

Reads and returns a fixnum from an input stream


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
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 115

def read_fixnum
  %x{
    var x, i, c = (#{read_byte} ^ 128) - 128;
    if (c === 0) {
      return 0;
    }

    if (c > 0) {
      if (4 < c && c < 128) {
        return c - 5;
      }
      x = 0;
      for (i = 0; i < c; i++) {
        x |= (#{read_byte} << (8*i));
      }
    } else {
      if (-129 < c && c < -4) {
        return c + 5;
      }

      c = -c;
      x = -1;

      for (i = 0; i < c; i++) {
        x &= ~(0xff << (8*i));
        x |= (#{read_byte} << (8*i));
      }
    }

    return x;
  }
end

#read_floatObject

Reads and returns Float from an input stream

Is encoded as 'f', '123.456'

Examples:

123.456

155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 155

def read_float
  s = read_string(cache: false)
  result = if s == 'nan'
             0.0 / 0
           elsif s == 'inf'
             1.0 / 0
           elsif s == '-inf'
             -1.0 / 0
           else
             s.to_f
           end
  @object_cache << result
  result
end

#read_hash(cache: true) ⇒ Object

Reads and returns a hash from an input stream Sometimes hash shouldn't be cached using an internal object cache, for a:

  • hash of instance variables
  • hash of struct attributes

is encoded as '{', 2, 100, 200, 300, 400

Examples:

{100 => 200, 300 => 400}

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 269

def read_hash(cache: true)
  result = {}

  if cache
    @object_cache << result
  end

  length = read_fixnum
  %x{
    if (length > 0) {
      var key, value, i;
      for (i = 0; i < #{length}; i++) {
        key = #{read};
        value = #{read};
        #{result[`key`] = `value`};
      }
    }
    return result;
  }
end

#read_hashdefObject

Reads and returns a hash with default value

is encoded as '}', 1, 100, 200, :default

Examples:

Hash.new(:default).merge(100 => 200)

297
298
299
300
301
302
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 297

def read_hashdef
  hash = read_hash
  default_value = read
  hash.default = default_value
  hash
end

#read_moduleObject

Reads and returns a Module from an input stream

is encoded as 'm', 'Kernel'

Examples:

Kernel

362
363
364
365
366
367
368
369
370
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 362

def read_module
  mod_name = read_string(cache: false)
  result = safe_const_get(mod_name)
  unless result.class == Module
    raise ArgumentError, "#{mod_name} does not refer to a Module"
  end
  @object_cache << result
  result
end

#read_objectObject

Reads and returns an abstract object from an input stream

is encoded as 'o', :Object, => 100

The only exception is a Range class (and its subclasses) For some reason in MRI isntances of this class have instance variables

  • begin
  • end
  • excl without '@' perfix.

Examples:

obj = Object.new
obj.instance_variable_set(:@ivar, 100)
obj

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 388

def read_object
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)

  object = klass.allocate
  @object_cache << object

  ivars = read_hash(cache: false)
  ivars.each do |name, value|
    if name[0] == '@'
      object.instance_variable_set(name, value)
    else
      # MRI allows an object to have ivars that do not start from '@'
      # https://github.com/ruby/ruby/blob/ab3a40c1031ff3a0535f6bcf26de40de37dbb1db/range.c#L1225
      `object[name] = value`
    end
  end

  object
end

#read_primitive_with_ivarsObject

Reads a primitive object with instance variables (classes that have their own marshalling rules, like Array/Hash/Regexp/etc)

is encoded as 'I', [100, 200, 300], => value

Examples:

arr = [100, 200, 300]
arr.instance_variable_set(:@ivar, :value)
arr

454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 454

def read_primitive_with_ivars
  object = read

  primitive_ivars = read_hash(cache: false)

  if primitive_ivars.any? && object.is_a?(String)
    object = `new String(object)`
  end

  primitive_ivars.each do |name, value|
    if name != 'E'
      object.instance_variable_set(name, value)
    end
  end

  object
end

#read_regexpObject

Reads and returns Regexp from an input stream

is encoded as '/', 'regexp', r.options.chr

Examples:

r = /regexp/mix

311
312
313
314
315
316
317
318
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 311

def read_regexp
  string = read_string(cache: false)
  options = read_byte

  result = Regexp.new(string, options)
  @object_cache << result
  result
end

#read_string(cache: true) ⇒ Object

Reads and returns a string from an input stream Sometimes string shouldn't be cached using an internal object cache, for a:

  • class/module name
  • string representation of float
  • string representation of regexp

191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 191

def read_string(cache: true)
  length = read_fixnum
  %x{
    var i, result = '';

    for (i = 0; i < length; i++) {
      result += #{read_char};
    }

    if (cache) {
      self.object_cache.push(result);
    }

    return result;
  }
end

#read_structObject

Reads and returns a Struct from an input stream

is encoded as 'S', :Point, => 100, :y => 200

Examples:

Point = Struct.new(:x, :y)
Point.new(100, 200)

328
329
330
331
332
333
334
335
336
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 328

def read_struct
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)
  attributes = read_hash(cache: false)
  args = attributes.values_at(*klass.members)
  result = klass.new(*args)
  @object_cache << result
  result
end

#read_symbolObject

Reads and returns a symbol from an input stream


210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 210

def read_symbol
  length = read_fixnum
  %x{
    var i, result = '';

    for (i = 0; i < length; i++) {
      result += #{read_char};
    }

    self.symbols_cache.push(result);

    return result;
  }
end

#read_user_classObject

Reads and User Class (instance of String/Regexp/Array/Hash subclass)

is encoded as 'C', :UserArray, [100, 200, 300]

Examples:

UserArray = Class.new(Array)
UserArray[100, 200, 300]

480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 480

def read_user_class
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)
  value = read(cache: false)

  result = if klass < Hash
             klass[value]
           else
             klass.new(value)
           end

  @object_cache << result

  result
end

#read_user_definedObject

Reads a 'User Defined' object that has '_dump/self._load' methods

is encoded as 'u', :UserDefined, '_dumped'

To load it back UserDefined._load' must be used.

Examples:

class UserDefined
  def _dump(level)
    '_dumped'
  end
end

UserDefined.new

511
512
513
514
515
516
517
518
519
520
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 511

def read_user_defined
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)
  data = read_string(cache: false)
  result = klass._load(data)

  @object_cache << result

  result
end

#read_user_marshalObject

Reads a 'User Marshal' object that has 'marshal_dump/marshal_load' methods

is encoded as 'U', :UserMarshal, [100, 200]

To load it back UserMarshal.allocate and UserMarshal#marshal_load must be called

Examples:

class UserMarshal < Struct.new(:a, :b)
  def marshal_dump
    [a, b]
  end

  def marshal_load(data)
    self.a, self.b = data
  end
end

UserMarshal.new(100, 200)

541
542
543
544
545
546
547
548
549
550
551
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 541

def read_user_marshal
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)

  result = klass.allocate
  @object_cache << result

  data = read(cache: false)
  result.marshal_load(data)
  result
end

#safe_const_get(const_name) ⇒ Object

Returns a constant by passed const_name, re-raises Marshal-specific error when it's missing


556
557
558
559
560
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 556

def safe_const_get(const_name)
  Object.const_get(const_name)
rescue NameError
  raise ArgumentError, "undefined class/module #{const_name}"
end