Class: RubyBreaker::Runtime::TypeSystem

Inherits:
Object
  • Object
show all
Includes:
Pluggable, TypeDefs
Defined in:
lib/rubybreaker/runtime/type_system.rb

Overview

This is the default type system for RubyBreaker. It can be overridden by a user specified type system. See pluggable.rb for how this can be done.

Instance Method Summary collapse

Methods included from Pluggable

#break_after_method_call, #break_before_method_call, #check_after_method_call, #check_before_method_call

Instance Method Details

#break_after_method(obj, meth_info) ⇒ Object

This method occurs after every call to a “monitored” method call of a module/class specified for “breaking”. It updates the type information.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/rubybreaker/runtime/type_system.rb', line 386

def break_after_method(obj, meth_info)

  is_obj_mod = (obj.class == Class or obj.class == Module)
  mod = is_obj_mod ? Runtime.eigen_class(obj) : obj.class

  # Take things out of the method info object
  meth_name = meth_info.meth_name
  retval = meth_info.ret
  args = meth_info.args
  blk = meth_info.blk

  RubyBreaker.log("break_after_method #{mod}##{meth_name} started")

  # Compute the least upper bound
  lub(obj, TYPE_MAP[mod], meth_name, retval, *args, &blk)

  if obj == retval  
    # It is possible that the method receiver is a wrapped object if
    # it is an argument to a method in the current call stack. So this
    # check is to return the wrapped object and not the stripped off
    # version. (Remember, == is overridden for the wrapped object.)
    meth_info.ret = obj
  end

  RubyBreaker.log("break_after_method #{mod}##{meth_name} ended")

end

#break_before_method(obj, meth_info) ⇒ Object

This method occurs before every call to a “monitored” method in a module/class specified for breaking. It wraps each argument with the object wrapper so it can be tracked of the method calls.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/rubybreaker/runtime/type_system.rb', line 314

def break_before_method(obj, meth_info)

  # Use the eigen class if the object is a module/class and use the
  # object's class otherwise.
  is_obj_mod = (obj.class == Class or obj.class == Module)
  mod = is_obj_mod ? Runtime.eigen_class(obj) : obj.class

  # Let's take things out of the MethodInfo object
  meth_name = meth_info.meth_name
  args = meth_info.args
  blk = meth_info.blk
  ret = meth_info.ret

  RubyBreaker.log("break_before_method #{mod}##{meth_name} started")
  
  args = args.map do |arg|
    if arg == nil || arg.kind_of?(TrueClass) || arg.kind_of?(FalseClass) 
      # XXX: would overrides resolve this issue?
      arg 
    elsif arg.respond_to?(WRAPPED_INDICATOR)
      # Don't need to wrap an object that is already wrapped
      arg
    else
      ObjectWrapper.new(arg)
    end
  end

  # Using this module object, retrieve the method type map which
  # maps method names to method types.
  meth_type_map = TYPE_MAP[mod]

  # Using the method name, get the type of this method from the map.
  meth_type = meth_type_map[meth_name]
  
  if meth_type
    # This means the method type has been created previously.
    unless !meth_type.instance_of?(MethodType) ||
           (blk == nil && meth_type.blk_type == nil) &&
           (!blk || blk.arity == meth_type.blk_type.arg_types.length)
      raise Errors::TypeError.new("Block usage is inconsistent")
    end
  else
    # No method type has been created for this method yet. Create a
    # blank method type (where each argument type, block type, and
    # return type are all nil).
    #
    # First, use the orignal method's arity to find out # of
    # arguments. 
    meth_obj = obj.method(Monitor.get_alt_meth_name(meth_name))
    arity = meth_obj.arity
    arg_types = [nil] * meth_obj.arity.abs 
    if blk
      # Do the same for the block too if there is one
      blk_arity = blk.arity
      blk_arg_types = [nil] * blk_artiy.abs
      blk_type = BlockType.new(blk_arg_types, nil, nil)
    else
      blk_type = nil
    end
    meth_type = MethodType.new(meth_name, arg_types, blk_type, nil)
    meth_type_map[meth_name] = meth_type
  end

  meth_info.args = args

  RubyBreaker.log("break_before_method #{mod}##{meth_name} ended")

end

#check_after_method(obj, meth_info) ⇒ Object

This method is invoked after the original method is executed.



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
309
# File 'lib/rubybreaker/runtime/type_system.rb', line 284

def check_after_method(obj, meth_info)
  is_obj_mod = (obj.class == Class or obj.class == Module)
  mod = is_obj_mod ? Runtime.eigen_class(obj) : obj.class

  # Get the method type map for the module/class from the global map.
  meth_type_map = TYPE_MAP[mod]
  return unless meth_type_map 

  # Let's take things out of the MethodInfo object
  meth_name = meth_info.meth_name
  ret = meth_info.ret

  RubyBreaker.log("check_after_method #{mod}##{meth_name} started")

  # Get the registered method type for this method
  meth_type = meth_type_map[meth_name]

  ret_type = NominalType.new(ret.class)
  if !meth_type.ret_type.subtype_of?(ret_type)
    msg = type_error_msg_prefix(mod, meth_name) +
          " return value does not have type #{ret_type.unparse()}."
    raise Errors::ReturnTypeError.new(msg)
  end

  RubyBreaker.log("check_after_method #{mod}##{meth_name} started")
end

#check_before_method(obj, meth_info) ⇒ Object

This method is invoked before the original method is executed.



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
# File 'lib/rubybreaker/runtime/type_system.rb', line 216

def check_before_method(obj, meth_info)
  is_obj_mod = (obj.class == Class or obj.class == Module)
  mod = is_obj_mod ? Runtime.eigen_class(obj) : obj.class

  meth_type_map = TYPE_MAP[mod]
  return unless meth_type_map

  # Let's take things out of the MethodInfo object
  meth_name = meth_info.meth_name
  args = meth_info.args
  # blk = meth_info.blk

  RubyBreaker.log("check_before_method #{mod}##{meth_name} started")

  # Get the registered method type for this method
  meth_type = meth_type_map[meth_name]

  # Do an arity check first. 
  if !arity_check(args.size, meth_type)
    msg = type_error_msg_prefix(mod, meth_name) +
          " has an arity of #{meth_type.arg_types.size} " +
          "but #{args.size} arguments were passed in"
    raise Errors::ArityError.new(msg)
  end

  # Remember what the last formal argument type was so that, if it is
  # a variable length argument type, we use to check the remaining
  # arguments.
  last_supertype = nil 

  # Check actual arguments up until the last position of the formal
  # argument type. If the number of the formal arguments is less than
  # actual arguments, it means the last formal argument is a variable
  # length. If it's the other way around, there are optional
  # arguments.
  meth_type.arg_types.each_with_index do |supertype, i|
    if supertype.kind_of?(OptionalType) ||
       supertype.kind_of?(VarLengthType)
      supertype = supertype.type
    end
    last_supertype = supertype
    break if i >= args.size 
    subtype = NominalType.new(args[i].class)
    if !subtype.subtype_of?(supertype)
      msg = type_error_msg_prefix(mod, meth_name) +
            "'s #{Util.ordinalize(i+1)} argument " +
            "does not have type #{supertype.unparse()}."
      raise Errors::ArgumentTypeError.new(msg)
    end
  end

  # Handle the remaining actual arguments
  if meth_type.arg_types.size < args.size
    for i in meth_type.arg_types.size..args.size-1
      subtype = NominalType.new(args[i].class)
      if !subtype.subtype_of?(last_supertype)
        msg = type_error_msg_prefix(mod, meth_name) +
              "'s #{Util.ordinalize(i+1)} argument " +
              "does not have type #{last_supertype.unparse()}."
        raise Errors::ArgumentTypeError.new(msg)
      end
    end
  end

  RubyBreaker.log("check_before_method #{mod}##{meth_name} ended")
end