Interesting! I tried another approach, a generic wrapping procedure.
Script below reported:
#<A:0x810bc78>: initialize: @init = “foo” (created)
#<A:0x810bc78>: a=: @a = 1 (created)
#<A:0x810bc78>: set_to_x: @a = “x” (was 1)
IVTRACED_REVISION =
‘$kNotwork: ivtraced.rb,v 1.6 2002/08/05 00:10:10 gotoken Exp $’
module MethodWrapped
GLOBAL_LOCK =
WRAPPED_METHODS = {}
def MethodWrapped.wrap(mod, id)
mod.module_eval do
arity = instance_method(id).arity
# to preserve arity…
if arity > 0
dummy_argument = (1…arity).map{|i| “arg#{i}”}.join(“,”)+“,”
elsif arity == 0
dummy_argument = “”
elsif arity == -1
dummy_argument = “*opt, "
else
dummy_argument = (1…-arity-1).map{|i| “arg#{i}”}.join(”,“)+”,*opt,"
end
da_bare= “#{dummy_argument}&block” # for ruby-mode.el
da = “(#{da_bare})” # for ruby-mode.el
module_eval src = (<<-EOS)
oldID = :#{id.to_s}
newID = :__method_wrapper_#{id.to_i}__
alias_method newID, oldID
def #{id.to_s}#{da}
cookie = [Time.now, rand]
type.method_started cookie,__id__,:#{id.to_s},#{da_bare}
begin
val = __method_wrapper_#{id.to_i}__#{da}
rescue Exception
bt = $!.backtrace
bt.delete_at(1)
$!.set_backtrace(bt)
raise
end
type.method_returned cookie,__id__,:#{id.to_s},val,#{da_bare}
val
end
MethodWrapped::WRAPPED_METHODS[mod] ||= {}
MethodWrapped::WRAPPED_METHODS[mod][oldID] = newID
EOS
end
end
def self.append_features(mod)
super
unless mod.public_methods.include?(“method_started”)
def mod.method_started(cookie, oid, mid, *args, &block)
# cookie: [time, float] formed unique array
# oid: object id (Fixnum)
# mid: method id (Symbol)
# args: array which was passed to mid
# block: proc which was passed to mid
end
end
unless mod.public_methods.include?("method_returned")
def mod.method_returned(cookie, oid, mid, val, *args, &block)
# cookie: identical to corresponding method_started
# oid: ditto
# mid: ditto
# args: ditto
# block: ditto
# val: retured value
end
end
if public_methods.include?("method_added")
mod.module_eval do
alias_method(:__original_method_added__, :method_added)
end
else
def mod.__original_method_added__(id) end
end
def mod.method_added(id)
val = __original_method_added__(id)
return val if MethodWrapped::GLOBAL_LOCK.first
MethodWrapped::GLOBAL_LOCK.push true
_old_critical = Thread.critical
begin
Thread.critical = true
MethodWrapped::wrap(self, id)
ensure
Thread.critical = _old_critical
MethodWrapped::GLOBAL_LOCK.pop
end
end
def mod.wrapped_methods
MethodWrapped::WRAPPED_METHODS[self].dup
end
end
end
module InstanceVariableTraced
private
def __ivtab
val = {}
instance_variables.each{|i| val[i] = instance_eval(i)}
val
end
IVT = InstanceVariableTraced
IVTRACE__ = {}
def self.append_features(mod)
super
mod.module_eval{include MethodWrapped}
unless mod.public_methods.include?("instance_variable_created")
def mod.instance_variable_created(obj, iv, mid, val)
# obj: receiver
# iv: instance variable name (String)
# mid: method id (Symbol)
# val: new value
$stderr.printf("#<%s:0x%x>: %s: %s = %s (created)\n",
self, obj.id<<1, mid, iv, val.inspect)
end
end
unless mod.public_methods.include?("instance_variable_set")
def mod.instance_variable_set(obj, iv, mid, old_val, val)
# obj: receiver
# iv: instance variable name (String)
# mid: method id (Symbol)
# old_val: old value
# val: new value
$stderr.printf("#<%s:0x%x>: %s: %s = %s (was %s)\n",
self, obj.id<<1, mid, iv, val.inspect, old_val.inspect)
end
end
def mod.method_started(cookie, oid, mid, *args)
return if mid == :__ivtab
IVT::IVTRACE__[cookie] = ObjectSpace._id2ref(oid).__send__(:__ivtab)
end
def mod.method_returned(cookie, oid, mid, val, *args)
return if mid == :__ivtab
begin
obj = ObjectSpace._id2ref(oid)
ivtrace_now = obj.__send__(:__ivtab)
ivtrace_old = IVT::IVTRACE__[cookie]
ivtrace_old.each{|iv,val_old|
val = ivtrace_now[iv]
if val_old != val
instance_variable_set(obj, iv, mid, val_old, val)
end
ivtrace_now.delete(iv)
}
ivtrace_now.each{|iv,val|
instance_variable_created(obj, iv, mid, val)
}
ensure
IVT::IVTRACE__.delete(cookie)
end
end
end
end
if FILE == $0
class A
include InstanceVariableTraced
attr_writer :a
def initialize
@init = "foo"
end
def set_to_x
@a = "x"
end
end
a = A.new
a.a = 1
a.set_to_x
end
···
At Mon, 5 Aug 2002 01:36:15 +0900, Massimiliano Mirra wrote:
module MonitorVariables
def self.append_features(klass)
klass.instance_methods.each do |method|
klass.class_eval <<-EOF
alias ___#{method} #{method}
def #{method}(*params,&block)
old_instance_variables = instance_variables
___#{method}(*params,&block)
diff = instance_variables - old_instance_variables
diff.empty? or puts "variable(s) " + diff.inspect + " added in method #{method}"
end
EOF
end
end
end