Class: AppMap::Hook

Inherits:
Object
  • Object
show all
Defined in:
lib/appmap/hook.rb,
lib/appmap/hook/method.rb,
ext/appmap/appmap.c

Defined Under Namespace

Classes: Method

Constant Summary collapse

LOG =
(ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
OBJECT_INSTANCE_METHODS =
%i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
OBJECT_STATIC_METHODS =
%i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Hook

Returns a new instance of Hook.



35
36
37
# File 'lib/appmap/hook.rb', line 35

def initialize(config)
  @config = config
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



34
35
36
# File 'lib/appmap/hook.rb', line 34

def config
  @config
end

Class Method Details

.lock_builtinsObject



16
17
18
19
20
# File 'lib/appmap/hook.rb', line 16

def lock_builtins
  return if @builtins_hooked

  @builtins_hooked = true
end

.qualify_method_name(method) ⇒ Object

Return the class, separator (‘.’ or ‘#’), and method name for the given method.



24
25
26
27
28
29
30
31
# File 'lib/appmap/hook.rb', line 24

def qualify_method_name(method)
  if method.owner.singleton_class?
    class_name = singleton_method_owner_name(method)
    [ class_name, '.', method.name ]
  else
    [ method.owner.name, '#', method.name ]
  end
end

.singleton_method_owner_name(method) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'ext/appmap/appmap.c', line 15

static VALUE
singleton_method_owner_name(VALUE klass, VALUE method)
{
  VALUE owner = rb_funcall(method, rb_intern("owner"), 0);
  VALUE attached = rb_ivar_get(owner, rb_intern("__attached__"));
  if (!CLASS_OR_MODULE_P(attached)) {
    attached = rb_funcall(attached, rb_intern("class"), 0);
  }

  // Did __attached__.class return an object that's a Module or a
  // Class?
  if (CLASS_OR_MODULE_P(attached)) {
    // Yup, get it's name
    return rb_mod_name(attached);
  }

  // Nope (which seems weird, but whatever). Fall back to calling
  // #to_s on the method's owner and hope for the best.
  return rb_funcall(owner, rb_intern("to_s"), 0);
}

Instance Method Details

#enable(&block) ⇒ Object

Observe class loading and hook all methods which match the config.



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
# File 'lib/appmap/hook.rb', line 40

def enable &block
  require 'appmap/hook/method'

  hook_builtins

  tp = TracePoint.new(:end) do |trace_point|
    cls = trace_point.self

    instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
    class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS

    hook = lambda do |hook_cls|
      lambda do |method_id|
        method = begin
          hook_cls.public_instance_method(method_id)
        rescue NameError
          warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
          return
        end

        warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG

        disasm = RubyVM::InstructionSequence.disasm(method)
        # Skip methods that have no instruction sequence, as they are obviously trivial.
        next unless disasm

        next unless \
          config.always_hook?(hook_cls, method.name) ||
          config.included_by_location?(method)

        hook_method = Hook::Method.new(config.package_for_method(method), hook_cls, method)

        # Don't try and trace the AppMap methods or there will be
        # a stack overflow in the defined hook method.
        next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)

        hook_method.activate
      end
    end

    instance_methods.each(&hook.(cls))
    class_methods.each(&hook.(cls.singleton_class))
  end

  tp.enable(&block)
end

#hook_builtinsObject



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
# File 'lib/appmap/hook.rb', line 87

def hook_builtins
  return unless self.class.lock_builtins

  class_from_string = lambda do |fq_class|
    fq_class.split('::').inject(Object) do |mod, class_name|
      mod.const_get(class_name)
    end
  end

  Config::BUILTIN_METHODS.each do |class_name, hook|
    require hook.package.package_name if hook.package.package_name
    Array(hook.method_names).each do |method_name|
      method_name = method_name.to_sym
      cls = class_from_string.(class_name)
      method = \
        begin
          cls.instance_method(method_name)
        rescue NameError
          cls.method(method_name) rescue nil
        end

      if method
        Hook::Method.new(hook.package, cls, method).activate
      else
        warn "Method #{method_name} not found on #{cls.name}"
      end
    end
  end
end