lib/appmap/hook/method.rb in appmap-0.77.3 vs lib/appmap/hook/method.rb in appmap-0.77.4

- old
+ new

@@ -1,57 +1,39 @@ # frozen_string_literal: true module AppMap - NEW_RUBY = Util.ruby_minor_version >= 2.7 - if NEW_RUBY && !Proc.instance_methods.include?(:ruby2_keywords) - warn "Ruby is #{RUBY_VERSION}, but Procs don't respond to #ruby2_keywords" - end - class Hook SIGNATURES = {} - LOOKUP_SIGNATURE = lambda do |id| method = super(id) - + signature = SIGNATURES[[ method.owner, method.name ]] if signature method.singleton_class.module_eval do define_method(:parameters) do signature end end end - + method end - + + # Single hooked method. + # Call #activate to override the original. class Method - attr_reader :hook_package, :hook_class, :hook_method + attr_reader :hook_package, :hook_class, :hook_method, :parameters, :arity - # +method_display_name+ may be nil if name resolution gets - # deferred until runtime (e.g. for a singleton method on an - # embedded Struct). - attr_reader :method_display_name - HOOK_DISABLE_KEY = 'AppMap::Hook.disable' private_constant :HOOK_DISABLE_KEY - # Grab the definition of Time.now here, to avoid interfering - # with the method we're hooking. - TIME_NOW = Time.method(:now) - private_constant :TIME_NOW - - ARRAY_OF_EMPTY_HASH = [{}.freeze].freeze - def initialize(hook_package, hook_class, hook_method) @hook_package = hook_package @hook_class = hook_class @hook_method = hook_method - - # Get the class for the method, if it's known. - @defined_class, method_symbol = Hook.qualify_method_name(@hook_method) - @method_display_name = [@defined_class, method_symbol, @hook_method.name].join if @defined_class + @parameters = hook_method.parameters + @arity = hook_method.arity end def activate if Hook::LOG msg = if method_display_name @@ -60,68 +42,10 @@ "#{hook_method.name} (class resolution deferred)" end warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}" end - defined_class = @defined_class - hook_package = self.hook_package - hook_method = self.hook_method - before_hook = self.method(:before_hook) - after_hook = self.method(:after_hook) - with_disabled_hook = self.method(:with_disabled_hook) - - is_array_containing_empty_hash = ->(obj) { - obj.is_a?(Array) && obj.length == 1 && obj[0].is_a?(Hash) && obj[0].size == 0 - } - - call_instance_method = lambda do |receiver, args, &block| - # https://github.com/applandinc/appmap-ruby/issues/153 - if NEW_RUBY && is_array_containing_empty_hash.(args) && hook_method.arity == 1 - hook_method.bind_call(receiver, {}, &block) - else - if NEW_RUBY - hook_method.bind_call(receiver, *args, &block) - else - hook_method.bind(receiver).call(*args, &block) - end - end - end - - hook_method_def = Proc.new do |*args, &block| - # We may not have gotten the class for the method during - # initialization (e.g. for a singleton method on an embedded - # struct), so make sure we have it now. - defined_class, = Hook.qualify_method_name(hook_method) unless defined_class - - reentrant = Thread.current[HOOK_DISABLE_KEY] - disabled_by_shallow_flag = \ - -> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package } - - enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call - - enabled = false if %i[instance_eval instance_exec].member?(hook_method.name) && args.empty? - - return call_instance_method.call(self, args, &block) unless enabled - - call_event, start_time = with_disabled_hook.call do - before_hook.call(self, defined_class, args) - end - return_value = nil - exception = nil - begin - return_value = call_instance_method.call(self, args, &block) - rescue - exception = $ERROR_INFO - raise - ensure - with_disabled_hook.call do - after_hook.call(self, call_event, start_time, return_value, exception) if call_event - end - end - end - hook_method_def = hook_method_def.ruby2_keywords if hook_method_def.respond_to?(:ruby2_keywords) - hook_method_parameters = hook_method.parameters.dup.freeze SIGNATURES[[ hook_class, hook_method.name ]] = hook_method_parameters # irb(main):001:0> Kernel.public_instance_method(:system) # (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError) @@ -138,32 +62,46 @@ end end protected - def before_hook(receiver, defined_class, args) - call_event = hook_package.handler_class.handle_call(defined_class, hook_method, receiver, args) - AppMap.tracing.record_event(call_event, package: hook_package, defined_class: defined_class, method: hook_method) if call_event - [ call_event, TIME_NOW.call ] + def gettime + Process.clock_gettime Process::CLOCK_MONOTONIC end - def after_hook(_receiver, call_event, start_time, return_value, exception) - elapsed = TIME_NOW.call - start_time - return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception) + def trace? + return false unless AppMap.tracing.enabled? + return false if Thread.current[HOOK_DISABLE_KEY] + return false if hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package + + true + end + + def method_display_name + return @method_display_name if @method_display_name + + return @method_display_name = [defined_class, method_symbol, hook_method.name].join if defined_class + + "#{hook_method.name} (class resolution deferred)" + end + + def defined_class + @defined_class ||= Hook.qualify_method_name(hook_method)&.first + end + + def after_hook(_receiver, call_event, elapsed, return_value, exception) + return_event = handle_return(call_event.id, elapsed, return_value, exception) AppMap.tracing.record_event(return_event) if return_event - nil end - def with_disabled_hook(&function) + def with_disabled_hook # Don't record functions, such as to_s and inspect, that might be called # by the fn. Otherwise there can be a stack overflow. Thread.current[HOOK_DISABLE_KEY] = true - begin - function.call - ensure - Thread.current[HOOK_DISABLE_KEY] = false - end + yield + ensure + Thread.current[HOOK_DISABLE_KEY] = false end end end module ObjectMethods @@ -186,6 +124,12 @@ unless ENV['APPMAP_NO_PATCH_MODULE'] == 'true' class Module prepend AppMap::ModuleMethods end +end + +if RUBY_VERSION < '3' + require 'appmap/hook/method/ruby2' +else + require 'appmap/hook/method/ruby3' end