appmap 0.77.3 → 0.77.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -7
- data/CHANGELOG.md +7 -0
- data/lib/appmap/handler/function.rb +8 -8
- data/lib/appmap/handler/net_http.rb +19 -18
- data/lib/appmap/handler/rails/request_handler.rb +3 -4
- data/lib/appmap/handler/rails/template.rb +68 -62
- data/lib/appmap/hook/method/ruby2.rb +56 -0
- data/lib/appmap/hook/method/ruby3.rb +56 -0
- data/lib/appmap/hook/method.rb +42 -98
- data/lib/appmap/hook.rb +2 -2
- data/lib/appmap/version.rb +1 -1
- data/spec/hook_spec.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a3775abc9bff6d949fa377e2da5121414500735c37d9c5b9d6f03a7a2ae31a7
|
4
|
+
data.tar.gz: 7254e2e5c3f25f431cdb25e8d0512d55a155afd8b64647b255b3a8223b663f3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf54fd237e40750eff85fb4723651a747ba6e69214ea04acae1a95964a09e4a800ea1de31dbc4f7dec7d26c585ef7fb9167a3e97eb36e9b0c14003240696d6d6
|
7
|
+
data.tar.gz: 81f413c5e21379b145a99f9882db9faea3d27e75ef56df7a8a928ce0b74f141c26025300cf0449fbf00128eb4991d99c94316f4fb522c5bd25042fb6ad2cfff3
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## [0.77.4](https://github.com/applandinc/appmap-ruby/compare/v0.77.3...v0.77.4) (2022-04-04)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Update Rails request handler to the new hook architecture ([595b39a](https://github.com/applandinc/appmap-ruby/commit/595b39abb030c1dcf85c83e4717c25d4c5177d4d))
|
7
|
+
|
1
8
|
## [0.77.3](https://github.com/applandinc/appmap-ruby/compare/v0.77.2...v0.77.3) (2022-03-29)
|
2
9
|
|
3
10
|
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'appmap/event'
|
4
|
+
require 'appmap/hook/method'
|
4
5
|
|
5
6
|
module AppMap
|
6
7
|
module Handler
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
# Base handler class, will emit method call and return events.
|
9
|
+
class Function < Hook::Method
|
10
|
+
def handle_call(receiver, args)
|
11
|
+
AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
14
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
15
|
+
AppMap::Event::MethodReturn.build_from_invocation(call_event_id, return_value, exception, elapsed: elapsed)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'appmap/event'
|
4
|
+
require 'appmap/hook/method'
|
4
5
|
require 'appmap/util'
|
5
6
|
require 'rack'
|
6
7
|
|
@@ -81,29 +82,29 @@ module AppMap
|
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
84
|
-
class
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
# Handler class for HTTP requests.
|
86
|
+
# Emits HTTP request events instead of method calls.
|
87
|
+
class NetHTTP < Hook::Method
|
88
|
+
def self.copy_headers(obj)
|
89
|
+
{}.tap do |headers|
|
90
|
+
obj.each_header do |key, value|
|
91
|
+
key = key.split('-').map(&:capitalize).join('-')
|
92
|
+
headers[key] = value
|
92
93
|
end
|
93
94
|
end
|
95
|
+
end
|
94
96
|
|
95
|
-
|
96
|
-
|
97
|
-
|
97
|
+
def handle_call(receiver, args)
|
98
|
+
# request will call itself again in a start block if it's not already started.
|
99
|
+
return unless receiver.started?
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
http = receiver
|
102
|
+
request = args.first
|
103
|
+
HTTPClientRequest.new(http, request)
|
104
|
+
end
|
103
105
|
|
104
|
-
|
105
|
-
|
106
|
-
end
|
106
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
107
|
+
HTTPClientResponse.new(return_value, call_event_id, elapsed)
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
@@ -100,15 +100,14 @@ module AppMap
|
|
100
100
|
|
101
101
|
protected
|
102
102
|
|
103
|
-
def before_hook(receiver,
|
103
|
+
def before_hook(receiver, *)
|
104
104
|
call_event = HTTPServerRequest.new(receiver.request)
|
105
105
|
# http_server_request events are i/o and do not require a package name.
|
106
106
|
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
107
|
-
|
107
|
+
call_event
|
108
108
|
end
|
109
109
|
|
110
|
-
def after_hook(receiver, call_event,
|
111
|
-
elapsed = TIME_NOW.call - start_time
|
110
|
+
def after_hook(receiver, call_event, elapsed, *)
|
112
111
|
return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
|
113
112
|
AppMap.tracing.record_event return_event
|
114
113
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'appmap/handler/function'
|
3
4
|
require 'appmap/event'
|
4
5
|
|
5
6
|
module AppMap
|
@@ -9,7 +10,7 @@ module AppMap
|
|
9
10
|
LOG = (ENV['APPMAP_TEMPLATE_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
10
11
|
|
11
12
|
# All the code which is touched by the AppMap is recorded in the classMap.
|
12
|
-
# This duck-typed 'method' is used to represent a view template as a package,
|
13
|
+
# This duck-typed 'method' is used to represent a view template as a package,
|
13
14
|
# class, and method in the classMap.
|
14
15
|
# The class name is generated from the template path. The package name is
|
15
16
|
# 'app/views', and the method name is 'render'. The source location of the method
|
@@ -41,31 +42,31 @@ module AppMap
|
|
41
42
|
def package
|
42
43
|
'app/views'
|
43
44
|
end
|
44
|
-
|
45
|
+
|
45
46
|
def name
|
46
47
|
'render'
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
def source_location
|
50
51
|
path
|
51
52
|
end
|
52
|
-
|
53
|
+
|
53
54
|
def static
|
54
55
|
true
|
55
56
|
end
|
56
|
-
|
57
|
+
|
57
58
|
def comment
|
58
59
|
nil
|
59
60
|
end
|
60
|
-
|
61
|
+
|
61
62
|
def labels
|
62
63
|
[ 'mvc.template' ]
|
63
64
|
end
|
64
65
|
end
|
65
|
-
|
66
|
+
|
66
67
|
# TemplateCall is a type of function call which is specialized to view template rendering. Since
|
67
68
|
# there isn't really a perfect method in Rails to hook, this one is synthesized from the available
|
68
|
-
# information.
|
69
|
+
# information.
|
69
70
|
class TemplateCall < AppMap::Event::MethodEvent
|
70
71
|
# This is basically the +self+ parameter.
|
71
72
|
attr_reader :render_instance
|
@@ -75,19 +76,19 @@ module AppMap
|
|
75
76
|
attr_accessor :ready
|
76
77
|
|
77
78
|
alias ready? ready
|
78
|
-
|
79
|
+
|
79
80
|
def initialize(render_instance)
|
80
81
|
super :call
|
81
|
-
|
82
|
+
|
82
83
|
AppMap::Event::MethodEvent.build_from_invocation(:call, event: self)
|
83
84
|
@ready = false
|
84
85
|
@render_instance = render_instance
|
85
86
|
end
|
86
|
-
|
87
|
+
|
87
88
|
def static?
|
88
89
|
true
|
89
90
|
end
|
90
|
-
|
91
|
+
|
91
92
|
def to_h
|
92
93
|
super.tap do |h|
|
93
94
|
h[:defined_class] = path ? path.parameterize.underscore : 'inline_template'
|
@@ -103,71 +104,76 @@ module AppMap
|
|
103
104
|
end.compact
|
104
105
|
end
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
TEMPLATE_RENDERER = 'appmap.handler.rails.template.renderer'
|
108
109
|
|
109
110
|
# Hooks the ActionView::Resolver methods +find_all+, +find_all_anywhere+. The resolver is used
|
110
111
|
# during template rendering to lookup the template file path from parameters such as the
|
111
112
|
# template name, prefix, and partial (boolean).
|
112
|
-
class ResolverHandler
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
name, prefix, partial = args
|
117
|
-
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
118
|
-
|
119
|
-
AppMap::Handler::Function.handle_call(defined_class, hook_method, receiver, args)
|
120
|
-
end
|
113
|
+
class ResolverHandler < AppMap::Handler::Function
|
114
|
+
def handle_call(receiver, args)
|
115
|
+
name, prefix, partial = args
|
116
|
+
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
121
117
|
|
122
|
-
|
123
|
-
|
124
|
-
# template will be recorded in the classMap.
|
125
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
126
|
-
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
127
|
-
path_obj = Array(return_value).first
|
128
|
-
|
129
|
-
warn "Resolver return: #{path_obj}" if LOG
|
130
|
-
|
131
|
-
if path_obj
|
132
|
-
path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
133
|
-
path_obj.identifier
|
134
|
-
else
|
135
|
-
path_obj.inspect
|
136
|
-
end
|
137
|
-
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
138
|
-
AppMap.tracing.record_method(TemplateMethod.new(path))
|
139
|
-
renderer.path ||= path if renderer
|
140
|
-
end
|
118
|
+
super
|
119
|
+
end
|
141
120
|
|
142
|
-
|
143
|
-
|
121
|
+
# When the resolver returns, look to see if there is template rendering underway.
|
122
|
+
# If so, populate the template path. In all cases, add a TemplateMethod so that the
|
123
|
+
# template will be recorded in the classMap.
|
124
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
125
|
+
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
126
|
+
path_obj = Array(return_value).first
|
127
|
+
|
128
|
+
warn "Resolver return: #{path_obj}" if LOG
|
129
|
+
|
130
|
+
record_template_path renderer, path_obj
|
131
|
+
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
def record_template_path(renderer, path_obj)
|
136
|
+
return unless path_obj
|
137
|
+
|
138
|
+
path = path_from_obj path_obj
|
139
|
+
AppMap.tracing.record_method(TemplateMethod.new(path))
|
140
|
+
renderer.path ||= path if renderer
|
141
|
+
end
|
142
|
+
|
143
|
+
def path_from_obj(path_obj)
|
144
|
+
path =
|
145
|
+
if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
146
|
+
path_obj.identifier
|
147
|
+
else
|
148
|
+
path_obj.inspect
|
149
|
+
end
|
150
|
+
path = path[Dir.pwd.length + 1..] if path.index(Dir.pwd) == 0
|
151
|
+
path
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
147
155
|
# Hooks the ActionView::Renderer method +render+. This method is used by Rails to perform
|
148
156
|
# template rendering. The TemplateCall event which is emitted by this handler has a
|
149
|
-
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
150
|
-
class RenderHandler
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
warn "Renderer: #{options}" if LOG
|
157
|
-
|
158
|
-
TemplateCall.new(receiver).tap do |call|
|
159
|
-
Thread.current[TEMPLATE_RENDERER] ||= []
|
160
|
-
Thread.current[TEMPLATE_RENDERER] << call
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
165
|
-
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
166
|
-
template_call.ready = true
|
157
|
+
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
158
|
+
class RenderHandler < AppMap::Hook::Method
|
159
|
+
def handle_call(receiver, args)
|
160
|
+
# context, options
|
161
|
+
_, options = args
|
162
|
+
|
163
|
+
warn "Renderer: #{options}" if LOG
|
167
164
|
|
168
|
-
|
165
|
+
TemplateCall.new(receiver).tap do |call|
|
166
|
+
Thread.current[TEMPLATE_RENDERER] ||= []
|
167
|
+
Thread.current[TEMPLATE_RENDERER] << call
|
169
168
|
end
|
170
169
|
end
|
170
|
+
|
171
|
+
def handle_return(call_event_id, elapsed, _return_value, _exception)
|
172
|
+
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
173
|
+
template_call.ready = true
|
174
|
+
|
175
|
+
AppMap::Event::MethodReturnIgnoreValue.build_from_invocation(call_event_id, elapsed: elapsed)
|
176
|
+
end
|
171
177
|
end
|
172
178
|
end
|
173
179
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def ruby2_keywords(*); end unless respond_to?(:ruby2_keywords, true)
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
class Hook
|
7
|
+
# Delegation methods for Ruby 2.
|
8
|
+
# cf. https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
9
|
+
class Method
|
10
|
+
ruby2_keywords def call(receiver, *args, &block)
|
11
|
+
return do_call(receiver, *args, &block) unless trace?
|
12
|
+
|
13
|
+
call_event = with_disabled_hook { before_hook receiver, *args }
|
14
|
+
trace_call call_event, receiver, *args, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def before_hook(receiver, *args)
|
20
|
+
call_event = handle_call(receiver, args)
|
21
|
+
if call_event
|
22
|
+
AppMap.tracing.record_event \
|
23
|
+
call_event,
|
24
|
+
package: hook_package,
|
25
|
+
defined_class: defined_class,
|
26
|
+
method: hook_method
|
27
|
+
end
|
28
|
+
call_event
|
29
|
+
end
|
30
|
+
|
31
|
+
ruby2_keywords def do_call(receiver, *args, &block)
|
32
|
+
hook_method.bind(receiver).call(*args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
ruby2_keywords def trace_call(call_event, receiver, *args, &block)
|
36
|
+
start_time = gettime
|
37
|
+
begin
|
38
|
+
return_value = do_call(receiver, *args, &block)
|
39
|
+
rescue # rubocop:disable Style/RescueStandardError
|
40
|
+
exception = $ERROR_INFO
|
41
|
+
raise
|
42
|
+
ensure
|
43
|
+
with_disabled_hook { after_hook receiver, call_event, gettime - start_time, return_value, exception } \
|
44
|
+
if call_event
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def hook_method_def
|
49
|
+
this = self
|
50
|
+
proc { |*args, &block| this.call self, *args, &block }.tap do |hook|
|
51
|
+
hook.ruby2_keywords if hook.respond_to? :ruby2_keywords
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
class Hook
|
5
|
+
# Delegation methods for Ruby 3.
|
6
|
+
class Method
|
7
|
+
def call(receiver, *args, **kwargs, &block)
|
8
|
+
return do_call(receiver, *args, **kwargs, &block) unless trace?
|
9
|
+
|
10
|
+
call_event = with_disabled_hook { before_hook receiver, *args, **kwargs }
|
11
|
+
trace_call call_event, receiver, *args, **kwargs, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def before_hook(receiver, *args, **kwargs)
|
17
|
+
args = [*args, kwargs] if !kwargs.empty? || keyrest?
|
18
|
+
call_event = handle_call(receiver, args)
|
19
|
+
if call_event
|
20
|
+
AppMap.tracing.record_event \
|
21
|
+
call_event,
|
22
|
+
package: hook_package,
|
23
|
+
defined_class: defined_class,
|
24
|
+
method: hook_method
|
25
|
+
end
|
26
|
+
call_event
|
27
|
+
end
|
28
|
+
|
29
|
+
def keyrest?
|
30
|
+
@keyrest ||= parameters.map(&:last).include? :keyrest
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_call(receiver, *args, **kwargs, &block)
|
34
|
+
hook_method.bind_call(receiver, *args, **kwargs, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def trace_call(call_event, receiver, *args, **kwargs, &block)
|
38
|
+
start_time = gettime
|
39
|
+
begin
|
40
|
+
return_value = do_call(receiver, *args, **kwargs, &block)
|
41
|
+
rescue # rubocop:disable Style/RescueStandardError
|
42
|
+
exception = $ERROR_INFO
|
43
|
+
raise
|
44
|
+
ensure
|
45
|
+
with_disabled_hook { after_hook receiver, call_event, gettime - start_time, return_value, exception } \
|
46
|
+
if call_event
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def hook_method_def
|
51
|
+
this = self
|
52
|
+
proc { |*args, **kwargs, &block| this.call self, *args, **kwargs, &block }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AppMap
|
4
|
-
NEW_RUBY = Util.ruby_minor_version >= 2.7
|
5
|
-
if NEW_RUBY && !Proc.instance_methods.include?(:ruby2_keywords)
|
6
|
-
warn "Ruby is #{RUBY_VERSION}, but Procs don't respond to #ruby2_keywords"
|
7
|
-
end
|
8
|
-
|
9
4
|
class Hook
|
10
5
|
SIGNATURES = {}
|
11
|
-
|
12
6
|
LOOKUP_SIGNATURE = lambda do |id|
|
13
7
|
method = super(id)
|
14
|
-
|
8
|
+
|
15
9
|
signature = SIGNATURES[[ method.owner, method.name ]]
|
16
10
|
if signature
|
17
11
|
method.singleton_class.module_eval do
|
@@ -20,36 +14,24 @@ module AppMap
|
|
20
14
|
end
|
21
15
|
end
|
22
16
|
end
|
23
|
-
|
17
|
+
|
24
18
|
method
|
25
19
|
end
|
26
|
-
|
27
|
-
class Method
|
28
|
-
attr_reader :hook_package, :hook_class, :hook_method
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
attr_reader :
|
21
|
+
# Single hooked method.
|
22
|
+
# Call #activate to override the original.
|
23
|
+
class Method
|
24
|
+
attr_reader :hook_package, :hook_class, :hook_method, :parameters, :arity
|
34
25
|
|
35
26
|
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
36
27
|
private_constant :HOOK_DISABLE_KEY
|
37
28
|
|
38
|
-
# Grab the definition of Time.now here, to avoid interfering
|
39
|
-
# with the method we're hooking.
|
40
|
-
TIME_NOW = Time.method(:now)
|
41
|
-
private_constant :TIME_NOW
|
42
|
-
|
43
|
-
ARRAY_OF_EMPTY_HASH = [{}.freeze].freeze
|
44
|
-
|
45
29
|
def initialize(hook_package, hook_class, hook_method)
|
46
30
|
@hook_package = hook_package
|
47
31
|
@hook_class = hook_class
|
48
32
|
@hook_method = hook_method
|
49
|
-
|
50
|
-
|
51
|
-
@defined_class, method_symbol = Hook.qualify_method_name(@hook_method)
|
52
|
-
@method_display_name = [@defined_class, method_symbol, @hook_method.name].join if @defined_class
|
33
|
+
@parameters = hook_method.parameters
|
34
|
+
@arity = hook_method.arity
|
53
35
|
end
|
54
36
|
|
55
37
|
def activate
|
@@ -62,64 +44,6 @@ module AppMap
|
|
62
44
|
warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
|
63
45
|
end
|
64
46
|
|
65
|
-
defined_class = @defined_class
|
66
|
-
hook_package = self.hook_package
|
67
|
-
hook_method = self.hook_method
|
68
|
-
before_hook = self.method(:before_hook)
|
69
|
-
after_hook = self.method(:after_hook)
|
70
|
-
with_disabled_hook = self.method(:with_disabled_hook)
|
71
|
-
|
72
|
-
is_array_containing_empty_hash = ->(obj) {
|
73
|
-
obj.is_a?(Array) && obj.length == 1 && obj[0].is_a?(Hash) && obj[0].size == 0
|
74
|
-
}
|
75
|
-
|
76
|
-
call_instance_method = lambda do |receiver, args, &block|
|
77
|
-
# https://github.com/applandinc/appmap-ruby/issues/153
|
78
|
-
if NEW_RUBY && is_array_containing_empty_hash.(args) && hook_method.arity == 1
|
79
|
-
hook_method.bind_call(receiver, {}, &block)
|
80
|
-
else
|
81
|
-
if NEW_RUBY
|
82
|
-
hook_method.bind_call(receiver, *args, &block)
|
83
|
-
else
|
84
|
-
hook_method.bind(receiver).call(*args, &block)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
hook_method_def = Proc.new do |*args, &block|
|
90
|
-
# We may not have gotten the class for the method during
|
91
|
-
# initialization (e.g. for a singleton method on an embedded
|
92
|
-
# struct), so make sure we have it now.
|
93
|
-
defined_class, = Hook.qualify_method_name(hook_method) unless defined_class
|
94
|
-
|
95
|
-
reentrant = Thread.current[HOOK_DISABLE_KEY]
|
96
|
-
disabled_by_shallow_flag = \
|
97
|
-
-> { hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package }
|
98
|
-
|
99
|
-
enabled = true if AppMap.tracing.enabled? && !reentrant && !disabled_by_shallow_flag.call
|
100
|
-
|
101
|
-
enabled = false if %i[instance_eval instance_exec].member?(hook_method.name) && args.empty?
|
102
|
-
|
103
|
-
return call_instance_method.call(self, args, &block) unless enabled
|
104
|
-
|
105
|
-
call_event, start_time = with_disabled_hook.call do
|
106
|
-
before_hook.call(self, defined_class, args)
|
107
|
-
end
|
108
|
-
return_value = nil
|
109
|
-
exception = nil
|
110
|
-
begin
|
111
|
-
return_value = call_instance_method.call(self, args, &block)
|
112
|
-
rescue
|
113
|
-
exception = $ERROR_INFO
|
114
|
-
raise
|
115
|
-
ensure
|
116
|
-
with_disabled_hook.call do
|
117
|
-
after_hook.call(self, call_event, start_time, return_value, exception) if call_event
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
hook_method_def = hook_method_def.ruby2_keywords if hook_method_def.respond_to?(:ruby2_keywords)
|
122
|
-
|
123
47
|
hook_method_parameters = hook_method.parameters.dup.freeze
|
124
48
|
SIGNATURES[[ hook_class, hook_method.name ]] = hook_method_parameters
|
125
49
|
|
@@ -140,28 +64,42 @@ module AppMap
|
|
140
64
|
|
141
65
|
protected
|
142
66
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
67
|
+
def gettime
|
68
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
69
|
+
end
|
70
|
+
|
71
|
+
def trace?
|
72
|
+
return false unless AppMap.tracing.enabled?
|
73
|
+
return false if Thread.current[HOOK_DISABLE_KEY]
|
74
|
+
return false if hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package
|
75
|
+
|
76
|
+
true
|
147
77
|
end
|
148
78
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
79
|
+
def method_display_name
|
80
|
+
return @method_display_name if @method_display_name
|
81
|
+
|
82
|
+
return @method_display_name = [defined_class, method_symbol, hook_method.name].join if defined_class
|
83
|
+
|
84
|
+
"#{hook_method.name} (class resolution deferred)"
|
85
|
+
end
|
86
|
+
|
87
|
+
def defined_class
|
88
|
+
@defined_class ||= Hook.qualify_method_name(hook_method)&.first
|
89
|
+
end
|
90
|
+
|
91
|
+
def after_hook(_receiver, call_event, elapsed, return_value, exception)
|
92
|
+
return_event = handle_return(call_event.id, elapsed, return_value, exception)
|
152
93
|
AppMap.tracing.record_event(return_event) if return_event
|
153
|
-
nil
|
154
94
|
end
|
155
95
|
|
156
|
-
def with_disabled_hook
|
96
|
+
def with_disabled_hook
|
157
97
|
# Don't record functions, such as to_s and inspect, that might be called
|
158
98
|
# by the fn. Otherwise there can be a stack overflow.
|
159
99
|
Thread.current[HOOK_DISABLE_KEY] = true
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
Thread.current[HOOK_DISABLE_KEY] = false
|
164
|
-
end
|
100
|
+
yield
|
101
|
+
ensure
|
102
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
165
103
|
end
|
166
104
|
end
|
167
105
|
end
|
@@ -189,3 +127,9 @@ unless ENV['APPMAP_NO_PATCH_MODULE'] == 'true'
|
|
189
127
|
prepend AppMap::ModuleMethods
|
190
128
|
end
|
191
129
|
end
|
130
|
+
|
131
|
+
if RUBY_VERSION < '3'
|
132
|
+
require 'appmap/hook/method/ruby2'
|
133
|
+
else
|
134
|
+
require 'appmap/hook/method/ruby3'
|
135
|
+
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -118,7 +118,7 @@ module AppMap
|
|
118
118
|
cls, method = entry
|
119
119
|
return false if config.never_hook?(cls, method)
|
120
120
|
|
121
|
-
|
121
|
+
hook.package.handler_class.new(hook.package, cls, method).activate
|
122
122
|
end
|
123
123
|
|
124
124
|
methods = []
|
@@ -209,7 +209,7 @@ module AppMap
|
|
209
209
|
package = config.lookup_package(hook_cls, method)
|
210
210
|
next unless package
|
211
211
|
|
212
|
-
|
212
|
+
package.handler_class.new(package, hook_cls, method).activate
|
213
213
|
end
|
214
214
|
end
|
215
215
|
|
data/lib/appmap/version.rb
CHANGED
data/spec/hook_spec.rb
CHANGED
@@ -115,7 +115,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
115
115
|
require 'appmap/hook/method'
|
116
116
|
package = config.lookup_package(hook_cls, method)
|
117
117
|
expect(package).to be
|
118
|
-
hook_method = AppMap::
|
118
|
+
hook_method = AppMap::Handler::Function.new(package, hook_cls, method)
|
119
119
|
hook_method.activate
|
120
120
|
|
121
121
|
tracer = AppMap.tracing.trace
|
@@ -1188,7 +1188,7 @@ describe 'AppMap class Hooking', docker: false do
|
|
1188
1188
|
require 'appmap/hook/method'
|
1189
1189
|
|
1190
1190
|
pkg = AppMap::Config::Package.new('fixtures/hook/prependend_override')
|
1191
|
-
AppMap::
|
1191
|
+
AppMap::Handler::Function.new(pkg, PrependedClass, PrependedClass.public_instance_method(:say_hello)).activate
|
1192
1192
|
|
1193
1193
|
tracer = AppMap.tracing.trace
|
1194
1194
|
AppMap::Event.reset_id_counter
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.77.
|
4
|
+
version: 0.77.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -383,6 +383,8 @@ files:
|
|
383
383
|
- lib/appmap/handler/rails/template.rb
|
384
384
|
- lib/appmap/hook.rb
|
385
385
|
- lib/appmap/hook/method.rb
|
386
|
+
- lib/appmap/hook/method/ruby2.rb
|
387
|
+
- lib/appmap/hook/method/ruby3.rb
|
386
388
|
- lib/appmap/metadata.rb
|
387
389
|
- lib/appmap/middleware/remote_recording.rb
|
388
390
|
- lib/appmap/minitest.rb
|