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')

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Hook

Returns a new instance of Hook.



32
33
34
# File 'lib/appmap/hook.rb', line 32

def initialize(config)
  @config = config
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



31
32
33
# File 'lib/appmap/hook.rb', line 31

def config
  @config
end

Class Method Details

.lock_builtinsObject



13
14
15
16
17
# File 'lib/appmap/hook.rb', line 13

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.



21
22
23
24
25
26
27
28
# File 'lib/appmap/hook.rb', line 21

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.



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

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)
    class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods

    hook = lambda do |hook_cls|
      lambda do |method_id|
        method = hook_cls.public_instance_method(method_id)

        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



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

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