Class: AppMap::Hook
- Defined in:
- lib/appmap/hook.rb,
lib/appmap/hook/method.rb,
lib/appmap/hook/method/ruby2.rb,
lib/appmap/hook/method/ruby3.rb,
lib/appmap/hook/record_around.rb,
ext/appmap/appmap.c
Defined Under Namespace
Modules: RecordAround Classes: Method
Constant Summary collapse
- 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
- SLOW_PACKAGE_THRESHOLD =
0.001
- SIGNATURES =
{}
- LOOKUP_SIGNATURE =
lambda do |id| method = super(id) hash_key = Hook.method_hash_key(method.owner, method) return method unless hash_key signature = SIGNATURES[hash_key] if signature method.singleton_class.module_eval do define_method(:parameters) do signature end end end method end
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Class Method Summary collapse
- .already_hooked?(method) ⇒ Boolean
- .hook_builtins? ⇒ Boolean
- .method_hash_key(cls, method) ⇒ Object
-
.qualify_method_name(method) ⇒ Object
Return the class, separator (‘.’ or ‘#’), and method name for the given method.
- .singleton_method_owner_name(method) ⇒ Object
Instance Method Summary collapse
-
#enable(&block) ⇒ Object
Observe class loading and hook all methods which match the config.
-
#hook_builtins ⇒ Object
hook_builtins builds hooks for code that is built in to the Ruby standard library.
-
#initialize(config) ⇒ Hook
constructor
A new instance of Hook.
Constructor Details
#initialize(config) ⇒ Hook
Returns a new instance of Hook.
57 58 59 60 |
# File 'lib/appmap/hook.rb', line 57 def initialize(config) @config = config @trace_enabled = [] end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
55 56 57 |
# File 'lib/appmap/hook.rb', line 55 def config @config end |
Class Method Details
.already_hooked?(method) ⇒ Boolean
35 36 37 38 39 40 41 |
# File 'lib/appmap/hook.rb', line 35 def already_hooked?(method) # After a method is defined, the statement "module_function <the-method>" can convert that method # into a module (class) method. The method is hooked first when it's defined, then AppMap will attempt to # hook it again when it's redefined as a module method. So we check the method source location - if it's # part of the AppMap source tree, we ignore it. method.source_location && method.source_location[0].index(__dir__) == 0 end |
.hook_builtins? ⇒ Boolean
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/appmap/hook.rb', line 24 def hook_builtins? Mutex.new.synchronize do @hook_builtins = true if @hook_builtins.nil? next false unless @hook_builtins @hook_builtins = false true end end |
.method_hash_key(cls, method) ⇒ Object
8 9 10 11 12 |
# File 'lib/appmap/hook/method.rb', line 8 def method_hash_key(cls, method) [cls, method.name].hash rescue TypeError => e warn "Error building hash key for #{cls}, #{method}: #{e}" end |
.qualify_method_name(method) ⇒ Object
Return the class, separator (‘.’ or ‘#’), and method name for the given method.
45 46 47 48 49 50 51 52 |
# File 'lib/appmap/hook.rb', line 45 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
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'ext/appmap/appmap.c', line 17
static VALUE
singleton_method_owner_name(VALUE klass, VALUE method)
{
VALUE owner = rb_funcall(method, rb_intern("owner"), 0);
#if RUBY_API_VERSION_CODE < 30200
VALUE attached = rb_ivar_get(owner, rb_intern("__attached__"));
#else
VALUE attached = rb_class_attached_object(owner);
#endif
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.
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 |
# File 'lib/appmap/hook.rb', line 63 def enable(&block) require "appmap/hook/method" hook_builtins # Paths that are known to be non-tracing. @notrace_paths = Set.new # Locations that have already been visited. @trace_locations = Set.new @module_load_times = Hash.new { |memo, k| memo[k] = 0 } @slow_packages = Set.new if ENV["APPMAP_PROFILE_HOOK"] == "true" dump_times = lambda do @module_load_times .keys .select { |key| !@slow_packages.member?(key) } .each do |key| elapsed = @module_load_times[key] if elapsed >= SLOW_PACKAGE_THRESHOLD @slow_packages.add(key) warn "AppMap: Package #{key} took #{@module_load_times[key]} seconds to hook" end end end at_exit(&dump_times) Thread.new do while true dump_times.call sleep 5 end end end @trace_end = TracePoint.new(:end, &method(:trace_end)) @trace_end.enable(&block) end |
#hook_builtins ⇒ Object
hook_builtins builds hooks for code that is built in to the Ruby standard library. No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 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 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/appmap/hook.rb', line 104 def hook_builtins return unless self.class.hook_builtins? hook_loaded_code = lambda do |hooks_by_class, builtin| hooks_by_class.each do |class_name, hooks| Array(hooks).each do |hook| HookLog.builtin class_name do if builtin && hook.package.require_name && hook.package.require_name != "ruby" begin require hook.package.require_name rescue HookLog.load_error hook.package.require_name, "Unable to require #{hook.package.require_name}: #{$!}" if HookLog.enabled? next end end begin base_cls = Object.const_get class_name rescue NameError HookLog.load_error class_name, "Class #{class_name} not found in global scope" if HookLog.enabled? next end Array(hook.method_names).each do |method_name| method_name = method_name.to_sym hook_method = lambda do |entry| cls, method = entry next if config.never_hook?(cls, method) hook.package.handler_class.new(hook.package, cls, method).activate end methods = [] # irb(main):001:0> Kernel.public_instance_method(:system) # (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError) if base_cls == Kernel begin methods << [base_cls, base_cls.instance_method(method_name)] rescue nil end end begin methods << [base_cls, base_cls.public_instance_method(method_name)] rescue nil end begin methods << [base_cls, base_cls.protected_instance_method(method_name)] rescue nil end if base_cls.respond_to?(:singleton_class) begin methods << [base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name)] rescue nil end begin methods << [base_cls.singleton_class, base_cls.singleton_class.protected_instance_method(method_name)] rescue nil end end methods.compact! if methods.empty? HookLog.load_error [base_cls.name, method_name].join("[#.]"), "Method #{method_name} not found on #{base_cls.name}" if HookLog.enabled? else methods.each(&hook_method) end end end end end end hook_loaded_code.call(config.builtin_hooks, true) end |