Skip to content

Commit 936bba4

Browse files
authored
fix: Override method accessors to provide the correct signature (#223)
Override method accessors to provide the correct signature for hooked methods
1 parent 49693ee commit 936bba4

File tree

3 files changed

+117
-4
lines changed

3 files changed

+117
-4
lines changed

lib/appmap/hook/method.rb

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ module AppMap
77
end
88

99
class Hook
10+
SIGNATURES = {}
11+
12+
LOOKUP_SIGNATURE = lambda do |id|
13+
method = super(id)
14+
15+
signature = SIGNATURES[[ method.owner, method.name ]]
16+
if signature
17+
method.singleton_class.module_eval do
18+
define_method(:parameters) do
19+
signature
20+
end
21+
end
22+
end
23+
24+
method
25+
end
26+
1027
class Method
1128
attr_reader :hook_package, :hook_class, :hook_method
1229

@@ -100,12 +117,10 @@ def activate
100117
hook_method_def = hook_method_def.ruby2_keywords if hook_method_def.respond_to?(:ruby2_keywords)
101118

102119
hook_method_parameters = hook_method.parameters.dup.freeze
120+
SIGNATURES[[ hook_class, hook_method.name ]] = hook_method_parameters
121+
103122
hook_class.ancestors.first.tap do |cls|
104123
cls.define_method_with_arity(hook_method.name, hook_method.arity, hook_method_def)
105-
redefined_method = cls.instance_method(hook_method.name)
106-
redefined_method.singleton_class.module_eval do
107-
define_method(:parameters) { hook_method_parameters }
108-
end
109124
end
110125
end
111126

@@ -136,4 +151,27 @@ def with_disabled_hook(&function)
136151
end
137152
end
138153
end
154+
155+
module ObjectMethods
156+
define_method(:method, AppMap::Hook::LOOKUP_SIGNATURE)
157+
define_method(:public_method, AppMap::Hook::LOOKUP_SIGNATURE)
158+
define_method(:singleton_method, AppMap::Hook::LOOKUP_SIGNATURE)
159+
end
160+
161+
module ModuleMethods
162+
define_method(:instance_method, AppMap::Hook::LOOKUP_SIGNATURE)
163+
define_method(:public_instance_method, AppMap::Hook::LOOKUP_SIGNATURE)
164+
end
165+
end
166+
167+
unless ENV['APPMAP_NO_PATCH_OBJECT'] == 'true'
168+
class Object
169+
prepend AppMap::ObjectMethods
170+
end
171+
end
172+
173+
unless ENV['APPMAP_NO_PATCH_MODULE'] == 'true'
174+
class Module
175+
prepend AppMap::ModuleMethods
176+
end
139177
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
class ReportParameters
3+
def to_s; self.class.name; end
4+
5+
def report_parameters(*args, kw1:, kw2: 'kw2', **kws)
6+
method(:report_parameters).parameters
7+
end
8+
end

spec/hook_spec.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ def test_hook_behavior(file, events_yaml, setup: nil, &block)
225225
:value: default
226226
YAML
227227
test_hook_behavior 'spec/fixtures/hook/instance_method.rb', events_yaml do
228+
expect(InstanceMethod.instance_method(:say_default).parameters).to eq([])
229+
expect(InstanceMethod.public_instance_method(:say_default).parameters).to eq([])
230+
expect(InstanceMethod.new.method(:say_default).parameters).to eq([])
231+
expect(InstanceMethod.new.public_method(:say_default).parameters).to eq([])
232+
expect { InstanceMethod.new.singleton_method(:say_default) }.to raise_error(NameError)
233+
228234
expect(InstanceMethod.new.say_default).to eq('default')
229235
end
230236
end
@@ -315,7 +321,22 @@ def test_hook_behavior(file, events_yaml, setup: nil, &block)
315321
:class: String
316322
:value: protected
317323
YAML
324+
parameters = []
318325
test_hook_behavior 'spec/fixtures/hook/protected_method.rb', events_yaml do
326+
expect(ProtectedMethod.singleton_method(:call_protected).parameters).to eq(parameters)
327+
expect(ProtectedMethod.instance_method(:call_protected).parameters).to eq(parameters)
328+
expect(ProtectedMethod.public_instance_method(:call_protected).parameters).to eq(parameters)
329+
expect(ProtectedMethod.new.method(:call_protected).parameters).to eq(parameters)
330+
expect(ProtectedMethod.new.public_method(:call_protected).parameters).to eq(parameters)
331+
expect { ProtectedMethod.new.singleton_method(:call_protected) }.to raise_error(NameError)
332+
333+
expect(ProtectedMethod.singleton_method(:protected_method).parameters).to eq(parameters)
334+
expect(ProtectedMethod.instance_method(:protected_method).parameters).to eq(parameters)
335+
expect(ProtectedMethod.public_instance_method(:protected_method).parameters).to eq(parameters)
336+
expect(ProtectedMethod.new.method(:protected_method).parameters).to eq(parameters)
337+
expect(ProtectedMethod.new.public_method(:protected_method).parameters).to eq(parameters)
338+
expect { ProtectedMethod.new.singleton_method(:protected_method) }.to raise_error(NameError)
339+
319340
expect(ProtectedMethod.new.call_protected).to eq('protected')
320341
end
321342
end
@@ -358,6 +379,7 @@ def test_hook_behavior(file, events_yaml, setup: nil, &block)
358379
:class: String
359380
:value: self.protected
360381
YAML
382+
parameters = []
361383
test_hook_behavior 'spec/fixtures/hook/protected_method.rb', events_yaml do
362384
expect(ProtectedMethod.call_protected).to eq('self.protected')
363385
end
@@ -1100,6 +1122,51 @@ def secure_compare(a, b)
11001122
end
11011123
end
11021124

1125+
it 'preserves the sighnature of hooked methods' do
1126+
parameters = []
1127+
events = <<~YAML
1128+
---
1129+
- :id: 1
1130+
:event: :call
1131+
:defined_class: ReportParameters
1132+
:method_id: report_parameters
1133+
:path: spec/fixtures/hook/report_parameters.rb
1134+
:lineno: 5
1135+
:static: false
1136+
:parameters:
1137+
- :name: :args
1138+
:class: Array
1139+
:value: '["foo"]'
1140+
:kind: :rest
1141+
- :name: :kw1
1142+
:class: String
1143+
:value: kw1
1144+
:kind: :keyreq
1145+
- :name: :kw2
1146+
:class: NilClass
1147+
:value: null
1148+
:kind: :key
1149+
- :name: :kws
1150+
:class: Hash
1151+
:value: "{}"
1152+
:kind: :keyrest
1153+
:receiver:
1154+
:class: ReportParameters
1155+
:value: ReportParameters
1156+
- :id: 2
1157+
:event: :return
1158+
:parent_id: 1
1159+
:return_value:
1160+
:class: Array
1161+
:value: "[[:rest, :args], [:keyreq, :kw1], [:key, :kw2], [:keyrest, :kws]]"
1162+
YAML
1163+
parameters = [[:rest, :args], [:keyreq, :kw1], [:key, :kw2], [:keyrest, :kws]]
1164+
test_hook_behavior 'spec/fixtures/hook/report_parameters.rb', events do
1165+
expect(ReportParameters.instance_method(:report_parameters).parameters).to eq(parameters)
1166+
expect(ReportParameters.new.report_parameters('foo', kw1: 'kw1')).to eq(parameters)
1167+
end
1168+
end
1169+
11031170
describe 'kwargs handling' do
11041171
if ruby_2?
11051172
# https://github.com/applandinc/appmap-ruby/issues/153

0 commit comments

Comments
 (0)