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



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

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 = []
  @extended = []
  @ivars = []
end

Instance Attribute Details

#bufferObject (readonly)

Returns the value of attribute buffer



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def buffer
  @buffer
end

#extendedObject (readonly)

Returns the value of attribute extended



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def extended
  @extended
end

#indexObject (readonly)

Returns the value of attribute index



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def index
  @index
end

#object_cacheObject (readonly)

Returns the value of attribute object_cache



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def object_cache
  @object_cache
end

#symbols_cacheObject (readonly)

Returns the value of attribute symbols_cache



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def symbols_cache
  @symbols_cache
end

#user_classObject (readonly)

Returns the value of attribute user_class



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def user_class
  @user_class
end

#versionObject (readonly)

Returns the value of attribute version



18
19
20
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 18

def version
  @version
end

Instance Method Details

#apply_extends(object) ⇒ Object

Applies all saved extending modules on the passed object



358
359
360
361
362
363
364
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 358

def apply_extends(object)
  @extended.each do |e|
    mod = safe_const_get(e)
    object.extend(mod)
  end
  @extended = []
end

#get_user_classObject

Constantizes and resets saved user class



280
281
282
283
284
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 280

def get_user_class
  klass = safe_const_get(@user_class)
  @user_class = nil
  klass
end

#lengthObject



35
36
37
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 35

def length
  @buffer.length
end

#load_object(klass, args) ⇒ Object

Loads an instance of passed klass using default marshal hooks



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

def load_object(klass, args)
  return klass._load(args) if klass.respond_to?(:_load)
  instance = klass.allocate
  instance.marshal_load(args) if instance.respond_to?(:marshal_load)
  instance
end

#read(cache: true, ivar_index: nil) ⇒ Object



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

def read(cache: true, ivar_index: nil)
  code = read_char
  # The first character indicates the type of the object
  result = case code
  when '0'
    nil
  when 'T'
    true
  when 'F'
    false
  when 'i'
    read_fixnum
  when 'e'
    read_extended
    object = read
    apply_extends(object)
    object
  when 'C'
    read_user_class
    read
  when 'o'
    read_object
  when 'd'
    raise NotImplementedError, 'Data type cannot be demarshaled'
  when 'u'
    raise NotImplementedError, 'UserDef type cannot be demarshaled yet' # read_userdef
  when 'U'
    read_usrmarshal
  when 'f'
    read_float
  when 'l'
    read_bignum
  when '"'
    read_string(cache: cache)
  when '/'
    read_regexp
  when '['
    read_array
  when '{'
    read_hash
  when '}'
    raise NotImplementedError, 'Hashdef type cannot be demarshaled yet' # read_hashdef
  when 'S'
    read_struct
  when 'M'
    raise NotImplementedError, 'ModuleOld type cannot be demarshaled yet' # read_module_old
  when 'c'
    read_class
  when 'm'
    read_module
  when ':'
    read_symbol
  when ';'
    symbols_cache[read_fixnum]
  when 'I'
    ivar_index = @ivars.length
    @ivars << true
    object = read(cache: cache, ivar_index: ivar_index)
    set_ivars(object) if @ivars.pop
    object
  when '@'
    object_cache[read_fixnum]
  else
    raise ArgumentError, "dump format error"
  end
  result
end

#read_arrayObject

Reads and returns an array from an input stream



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 217

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



368
369
370
371
372
373
374
375
376
377
378
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 368

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



107
108
109
110
111
112
113
114
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 107

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

#read_charObject



116
117
118
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 116

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

#read_classObject

Reads and returns a Class from an input stream



288
289
290
291
292
293
294
295
296
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 288

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_extendedObject

Reads and saves a Module from an input stream that was extending marshalled object



351
352
353
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 351

def read_extended
  @extended << read
end

#read_fixnumObject

Reads and returns a fixnum from an input stream



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

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



382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 382

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


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

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_moduleObject

Reads and returns a Module from an input stream



300
301
302
303
304
305
306
307
308
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 300

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



312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 312

def read_object
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)
  result = if @ivars.last
    data = read_hash(cache: false)
    load_object(klass, data)
  else
    object = klass.allocate
    set_ivars(object)
    object
  end
  @object_cache << result
  result
end

#read_regexpObject

Reads and returns Regexp from an input stream



399
400
401
402
403
404
405
406
407
408
409
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 399

def read_regexp
  args = [read_string(cache: false), read_byte]

  result = if @user_class
    load_object(get_user_class, args)
  else
    load_object(Regexp, args)
  end
  @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
  • float
  • regexp


181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 181

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



339
340
341
342
343
344
345
346
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 339

def read_struct
  klass_name = read(cache: false)
  klass = safe_const_get(klass_name)
  args = read_hash(cache: false)
  result = load_object(klass, args)
  @object_cache << result
  result
end

#read_symbolObject

Reads and returns a symbol from an input stream



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 200

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 saves a user class from an input stream Used for cases like String/Array subclasses



274
275
276
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 274

def read_user_class
  @user_class = read(cache: false)
end

#read_usrmarshalObject

Reads and returns an abstract object from an input stream when the class of this object has custom marshalling rules



414
415
416
417
418
419
420
421
422
423
424
425
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 414

def read_usrmarshal
  klass_name = read
  klass = safe_const_get(klass_name)
  result = klass.allocate
  @object_cache << result
  data = read
  unless result.respond_to?(:marshal_load)
    raise TypeError, "instance of #{klass} needs to have method `marshal_load'"
  end
  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



263
264
265
266
267
268
269
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 263

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

#set_ivars(obj) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'opal/opal/corelib/marshal/read_buffer.rb', line 120

def set_ivars(obj)
  data = read_hash(cache: false)

  data.each do |ivar, value|
    case ivar
    when :E then # encodings are not supported
    when :encoding # encodings are not supported
    else
      if ivar.start_with?('@')
        obj.instance_variable_set(ivar, value)
      end
    end
  end

  if obj.respond_to?(:marshal_load)
    obj.marshal_load(data)
  end
end