appmap 0.47.0 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1f2b43daf0437ceb6073a64272148ca9168c4ac781c01e2e73c36259a3bcc7b
4
- data.tar.gz: 2a7275d2be15d2bfcf0c2dfc898807212840506c3692232b2d049bc771a4a3b9
3
+ metadata.gz: 12dbe41efff7d8fd40a884be376ff1579d961371b780eddf18d4724e7ce5997f
4
+ data.tar.gz: fb038cfbcc6c2432b780d4555b8948729fd65a2943a4bc1200c7d6943a62f5a8
5
5
  SHA512:
6
- metadata.gz: 5000bdc938facaa5e181de6e3d6023c6a1b1a1e48a260545ae3245236bf271e22ab6462c16d1c73fb44b4e9bc4e6533f8be12428e2d7ff9e1c06f65ec1992bf7
7
- data.tar.gz: 10db3cd34b1f9eb5f81070607d12b6a714b12735ac25828df4c6a3e50d0efa354ad9ff9206bc42cd2d7ccb61ffef83f663d41b3896928f0fd9bdd1e53ed4e543
6
+ metadata.gz: 07a9ec31b08915e1d630a90200393d88db2b85b49b8dc21ffa595e598fa57ff8ed3afa463e3e9dc2ae9485d5dff399eb1724c6aa38cbcceeb0d3c6c0757d0f70
7
+ data.tar.gz: 3c6e629772604730b6acc14409c98694624cbfcbbc14a661182b6bb5388664b15a918c3e82ca5b55dd1daf6ca142741d0380a3e88c152f5bafcd57cb9b38dd6c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.47.1](https://github.com/applandinc/appmap-ruby/compare/v0.47.0...v0.47.1) (2021-05-13)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Add the proper template function hooks for Rails 6.0.7 ([175f489](https://github.com/applandinc/appmap-ruby/commit/175f489acbaed77ad52a18d805e4b6eeae1abfdb))
7
+
1
8
  # [0.47.0](https://github.com/applandinc/appmap-ruby/compare/v0.46.0...v0.47.0) (2021-05-13)
2
9
 
3
10
 
data/lib/appmap/config.rb CHANGED
@@ -85,18 +85,7 @@ module AppMap
85
85
  end
86
86
  end
87
87
 
88
- Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
89
- def to_h
90
- {
91
- package: package,
92
- class: cls,
93
- labels: labels,
94
- functions: function_names.map(&:to_sym)
95
- }.compact
96
- end
97
- end
98
- private_constant :Function
99
-
88
+ # Identifies specific methods within a package which should be hooked.
100
89
  class TargetMethods # :nodoc:
101
90
  attr_reader :method_names, :package
102
91
 
@@ -118,28 +107,91 @@ module AppMap
118
107
  end
119
108
  private_constant :TargetMethods
120
109
 
121
- OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
110
+ # Function represents a specific function configured for hooking by the +functions+
111
+ # entry in appmap.yml. When the Config is initialized, each Function is converted into
112
+ # a Package and TargetMethods. It's called a Function rather than a Method, because Function
113
+ # is the AppMap terminology.
114
+ Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
115
+ def to_h
116
+ {
117
+ package: package,
118
+ class: cls,
119
+ labels: labels,
120
+ functions: function_names.map(&:to_sym)
121
+ }.compact
122
+ end
123
+ end
124
+ private_constant :Function
122
125
 
123
- # Methods that should always be hooked, with their containing
124
- # package and labels that should be applied to them.
125
- HOOKED_METHODS = {
126
- 'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.view], optional: true).tap do |package|
127
- package.handler_class = AppMap::Handler::Rails::Template::RenderHandler if package
128
- end),
129
- 'ActionView::Resolver' => TargetMethods.new(%i[find_all find_all_anywhere], Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.template.resolver], optional: true).tap do |package|
130
- package.handler_class = AppMap::Handler::Rails::Template::ResolverHandler if package
131
- end),
132
- 'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
133
- 'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
134
- 'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
135
- 'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
136
- 'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
137
- 'ActionController::Instrumentation' => [
138
- TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
139
- ]
140
- }.freeze
126
+ ClassTargetMethods = Struct.new(:cls, :target_methods) # :nodoc:
127
+ private_constant :ClassTargetMethods
128
+
129
+ MethodHook = Struct.new(:cls, :method_names, :labels) # :nodoc:
130
+ private_constant :MethodHook
131
+
132
+ class << self
133
+ def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
134
+ Array(methods).map do |method|
135
+ package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
136
+ next unless package
137
+
138
+ package.handler_class = handler_class if handler_class
139
+ ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
140
+ end.compact
141
+ end
142
+
143
+ def method_hook(cls, method_names, labels)
144
+ MethodHook.new(cls, method_names, labels)
145
+ end
146
+ end
147
+
148
+ # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
149
+ # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
150
+ # of appmap.yml.
151
+ METHOD_HOOKS = [
152
+ package_hooks('actionview',
153
+ [
154
+ method_hook('ActionView::Renderer', :render, %w[mvc.view]),
155
+ method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
156
+ method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
157
+ ],
158
+ handler_class: AppMap::Handler::Rails::Template::RenderHandler,
159
+ package_name: 'action_view'
160
+ ),
161
+ package_hooks('actionview',
162
+ [
163
+ method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
164
+ ],
165
+ handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
166
+ package_name: 'action_view'
167
+ ),
168
+ package_hooks('actionpack',
169
+ [
170
+ method_hook('ActionDispatch::Request::Session', %i[destroy [] dig values []= clear update delete fetch merge], %w[http.session]),
171
+ method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session]),
172
+ method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
173
+ ],
174
+ package_name: 'action_dispatch'
175
+ ),
176
+ package_hooks('cancancan',
177
+ [
178
+ method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
179
+ method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
180
+ ]
181
+ ),
182
+ package_hooks('actionpack',
183
+ [
184
+ method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
185
+ ],
186
+ package_name: 'action_controller'
187
+ )
188
+ ].flatten.freeze
141
189
 
142
- BUILTIN_METHODS = {
190
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
191
+
192
+ # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
193
+ # Therefore, we can't rely on TracePoint to report the loading of this code.
194
+ BUILTIN_HOOKS = {
143
195
  'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
144
196
  'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
145
197
  'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
@@ -166,26 +218,29 @@ module AppMap
166
218
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
167
219
  }.freeze
168
220
 
169
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_methods
221
+ attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
170
222
 
171
223
  def initialize(name, packages, exclude: [], functions: [])
172
224
  @name = name
173
225
  @packages = packages
174
- @hook_paths = packages.map(&:path)
226
+ @hook_paths = Set.new(packages.map(&:path))
175
227
  @exclude = exclude
176
- @builtin_methods = BUILTIN_METHODS
228
+ @builtin_hooks = BUILTIN_HOOKS
177
229
  @functions = functions
178
- @hooked_methods = HOOKED_METHODS.dup
230
+
231
+ @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
232
+ hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
233
+ end
234
+
179
235
  functions.each do |func|
180
236
  package_options = {}
181
237
  package_options[:labels] = func.labels if func.labels
182
- @hooked_methods[func.cls] ||= []
183
238
  @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
184
239
  end
185
240
 
186
241
  @hooked_methods.each_value do |hooks|
187
242
  Array(hooks).each do |hook|
188
- @hook_paths << hook.package.path if hook.package
243
+ @hook_paths << hook.package.path
189
244
  end
190
245
  end
191
246
  end
@@ -105,12 +105,18 @@ module AppMap
105
105
  # If so, populate the template path. In all cases, add a TemplateMethod so that the
106
106
  # template will be recorded in the classMap.
107
107
  def handle_return(call_event_id, elapsed, return_value, exception)
108
- warn "Resolver return: #{return_value.inspect}" if LOG
109
-
110
108
  renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
111
- path = Array(return_value).first&.inspect
109
+ path_obj = Array(return_value).first
110
+
111
+ warn "Resolver return: #{path_obj}" if LOG
112
112
 
113
- if path
113
+ if path_obj
114
+ path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
115
+ path_obj.identifier
116
+ else
117
+ path_obj.inspect
118
+ end
119
+ path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
114
120
  AppMap.tracing.record_method(TemplateMethod.new(path))
115
121
  renderer.path ||= path if renderer
116
122
  end
data/lib/appmap/hook.rb CHANGED
@@ -64,7 +64,7 @@ module AppMap
64
64
  end
65
65
  end
66
66
 
67
- config.builtin_methods.each do |class_name, hooks|
67
+ config.builtin_hooks.each do |class_name, hooks|
68
68
  Array(hooks).each do |hook|
69
69
  require hook.package.package_name if hook.package.package_name
70
70
  Array(hook.method_names).each do |method_name|
@@ -139,6 +139,8 @@ module AppMap
139
139
  # a stack overflow in the defined hook method.
140
140
  next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
141
141
 
142
+ next if method_id == :call
143
+
142
144
  method = begin
143
145
  hook_cls.public_instance_method(method_id)
144
146
  rescue NameError
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.47.0'
6
+ VERSION = '0.47.1'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.5.0'
9
9
  end
data/spec/hook_spec.rb CHANGED
@@ -64,65 +64,14 @@ describe 'AppMap class Hooking', docker: false do
64
64
  expect(config.never_hook?(ExcludeTest, ExcludeTest.method(:cls_method))).to be_truthy
65
65
  end
66
66
 
67
- it "handles an instance method named 'call' without issues" do
67
+ it "an instance method named 'call' will be ignored" do
68
68
  events_yaml = <<~YAML
69
- ---
70
- - :id: 1
71
- :event: :call
72
- :defined_class: MethodNamedCall
73
- :method_id: call
74
- :path: spec/fixtures/hook/method_named_call.rb
75
- :lineno: 8
76
- :static: false
77
- :parameters:
78
- - :name: :a
79
- :class: Integer
80
- :value: '1'
81
- :kind: :req
82
- - :name: :b
83
- :class: Integer
84
- :value: '2'
85
- :kind: :req
86
- - :name: :c
87
- :class: Integer
88
- :value: '3'
89
- :kind: :req
90
- - :name: :d
91
- :class: Integer
92
- :value: '4'
93
- :kind: :req
94
- - :name: :e
95
- :class: Integer
96
- :value: '5'
97
- :kind: :req
98
- :receiver:
99
- :class: MethodNamedCall
100
- :value: MethodNamedCall
101
- - :id: 2
102
- :event: :return
103
- :parent_id: 1
104
- :return_value:
105
- :class: String
106
- :value: 1 2 3 4 5
69
+ --- []
107
70
  YAML
108
71
 
109
72
  _, tracer = test_hook_behavior 'spec/fixtures/hook/method_named_call.rb', events_yaml do
110
73
  expect(MethodNamedCall.new.call(1, 2, 3, 4, 5)).to eq('1 2 3 4 5')
111
74
  end
112
- class_map = AppMap.class_map(tracer.event_methods)
113
- expect(Diffy::Diff.new(<<~CLASSMAP, YAML.dump(class_map)).to_s).to eq('')
114
- ---
115
- - :name: spec/fixtures/hook/method_named_call.rb
116
- :type: package
117
- :children:
118
- - :name: MethodNamedCall
119
- :type: class
120
- :children:
121
- - :name: call
122
- :type: function
123
- :location: spec/fixtures/hook/method_named_call.rb:8
124
- :static: false
125
- CLASSMAP
126
75
  end
127
76
 
128
77
  it 'can custom hook and label a function' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.47.0
4
+ version: 0.47.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin