appmap 0.47.0 → 0.47.1

Sign up to get free protection for your applications and to get access to all the features.
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