appmap 0.66.2 → 0.68.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: 2dd5295e0bbe8c9a4281b27bd712b61640672e93a03d6c3e31af3e201c9ca592
4
- data.tar.gz: 8fb74d28bb2367918831c6d3af029440591da8ee694f1645fc2ef8baf5b28b60
3
+ metadata.gz: 7da5837e4371cd2ce6aa6858e6ca88a50d38103657c054d0752ee7ec03d1f499
4
+ data.tar.gz: 81a5b8d75c7b4f5bad51a9f12cf3ea37ea38d1d7ca07a67073739cc96038775c
5
5
  SHA512:
6
- metadata.gz: ef07a3169b2d1f4dcabb406296275506250840cc767440b60a106f48ad5ee251017a9e9f7cd9769e7d0d133a3b9704f76bfef571fd69feef85163979cec80a80
7
- data.tar.gz: 34c9c18e230dc46d868b759422eb7aeaba79b99fd1cca610bbc1a8bdc70a7cfa18e34a064a43cc50847c8c452bf267a6003a5b98cb3a2e2e8fa40028552fe112
6
+ metadata.gz: 2c2b4f67ecaf2f47344635cf3d16cbc8ab8821e10cd24966ff708baaa8c35c6a71bdb6a76d0eaf9415596f6d05c8c5b04c57115fede5bd6f4a0b4a1743e4ebc9
7
+ data.tar.gz: 712feeb618cea02bbaf17711a8c292f1670ef8c62974e2d3c706af88a31b27872afe139e7fc409f1da9800021035544736f2754970b4817234fabdf0190b113e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## [0.68.1](https://github.com/applandinc/appmap-ruby/compare/v0.68.0...v0.68.1) (2021-11-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Support new style of `functions` syntax in appmap.yml ([dca327c](https://github.com/applandinc/appmap-ruby/commit/dca327c98db1bddf849056995541306a5fc07eea))
7
+
8
+ # [0.68.0](https://github.com/applandinc/appmap-ruby/compare/v0.67.1...v0.68.0) (2021-11-05)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Require weakref ([2f94f80](https://github.com/applandinc/appmap-ruby/commit/2f94f808bd3327aa3fc7fd8e6a3428a5da3a29bb))
14
+
15
+
16
+ ### Features
17
+
18
+ * Externalize config of hooks ([8080222](https://github.com/applandinc/appmap-ruby/commit/8080222ce5b61d9824eaf20410d7b9b94b679890))
19
+ * Support loading hook config via path env vars ([4856483](https://github.com/applandinc/appmap-ruby/commit/48564837784f8b0e87c4286ad3e2f6cb2d272dcf))
20
+
21
+ ## [0.67.1](https://github.com/applandinc/appmap-ruby/compare/v0.67.0...v0.67.1) (2021-11-02)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * Don't try to index AppMaps when inspecting ([ca18861](https://github.com/applandinc/appmap-ruby/commit/ca188619bd7085caa75a06eeeb5d5a92213251ac))
27
+
28
+ # [0.67.0](https://github.com/applandinc/appmap-ruby/compare/v0.66.2...v0.67.0) (2021-10-21)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * Ensure rack is available, and handle nil HTTP response ([5e81dc4](https://github.com/applandinc/appmap-ruby/commit/5e81dc4310c9b7f2d81c31339f8490639c845f76))
34
+ * Handle WeakRef ([852ee04](https://github.com/applandinc/appmap-ruby/commit/852ee047880f9d1492be38772ed3f0cc1a670cb5))
35
+
36
+
37
+ ### Features
38
+
39
+ * APPMAP_AUTOREQUIRE and APPMAP_INITIALIZE env vars to customize loading behavior ([369807e](https://github.com/applandinc/appmap-ruby/commit/369807e4c90243e296b324e70805bd09d0f5fc4a))
40
+ * Perform GC before running each test ([84c895e](https://github.com/applandinc/appmap-ruby/commit/84c895e95fe8caa270cc1412e239599bfcc1b467))
41
+
1
42
  ## [0.66.2](https://github.com/applandinc/appmap-ruby/compare/v0.66.1...v0.66.2) (2021-10-07)
2
43
 
3
44
 
@@ -0,0 +1,4 @@
1
+ - method: JSON::Ext::Parser#parse
2
+ label: format.json.parse
3
+ - method: JSON::Ext::Generator::State#generate
4
+ label: format.json.generate
@@ -0,0 +1,3 @@
1
+ - method: Net::HTTP#request
2
+ label: protocol.http
3
+ handler_class: AppMap::Handler::NetHTTP
@@ -0,0 +1,16 @@
1
+ - method: OpenSSL::PKey::PKey#sign
2
+ label: crypto.pkey
3
+ - methods:
4
+ - OpenSSL::X509::Request#sign
5
+ - OpenSSL::X509::Request#verify
6
+ label: crypto.x509
7
+ - method: OpenSSL::X509::Certificate#sign
8
+ label: crypto.x509
9
+ - methods:
10
+ - OpenSSL::PKCS5#pbkdf2_hmac
11
+ - OpenSSL::PKCS5#pbkdf2_hmac_sha1
12
+ label: crypto.pkcs5
13
+ - method: OpenSSL::Cipher#encrypt
14
+ label: crypto.encrypt
15
+ - method: OpenSSL::Cipher#decrypt
16
+ label: crypto.decrypt
@@ -0,0 +1,10 @@
1
+ - methods:
2
+ - Psych#load
3
+ - Psych#load_stream
4
+ - Psych#parse
5
+ - Psych#parse_stream
6
+ label: format.yaml.parse
7
+ - methods:
8
+ - Psych#dump
9
+ - Psych#dump_stream
10
+ label: format.yaml.generate
@@ -10,7 +10,14 @@ module AppMap
10
10
 
11
11
  class Validate < ValidateStruct
12
12
  def perform
13
- puts JSON.pretty_generate(config_validator.valid? ? [] : config_validator.violations.map(&:to_h))
13
+ schema_path = File.expand_path('../../../../../config-schema.yml', __FILE__)
14
+ schema = YAML.safe_load(File.read(schema_path))
15
+ result = {
16
+ version: 2,
17
+ errors: config_validator.valid? ? [] : config_validator.violations.map(&:to_h),
18
+ schema: schema
19
+ }
20
+ puts JSON.pretty_generate(result)
14
21
  end
15
22
 
16
23
  private
@@ -13,7 +13,6 @@ module AppMap
13
13
 
14
14
  def inspect(arguments)
15
15
  detect_nodejs
16
- index_appmaps
17
16
 
18
17
  arguments.unshift 'inspect'
19
18
  arguments.unshift APPMAP_JS
data/lib/appmap/config.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
3
4
  require 'set'
4
5
  require 'yaml'
5
6
  require 'appmap/util'
@@ -11,17 +12,20 @@ require 'appmap/depends/configuration'
11
12
 
12
13
  module AppMap
13
14
  class Config
14
- # Specifies a code +path+ to be mapped.
15
+ # Specifies a logical code package be mapped.
16
+ # This can be a project source folder, a Gem, or a builtin.
17
+ #
15
18
  # Options:
16
19
  #
20
+ # * +path+ indicates a relative path to a code folder.
17
21
  # * +gem+ may indicate a gem name that "owns" the path
18
- # * +package_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
22
+ # * +require_name+ can be used to make sure that the code is required so that it can be loaded. This is generally used with
19
23
  # builtins, or when the path to be required is not automatically required when bundler requires the gem.
20
24
  # * +exclude+ can be used used to exclude sub-paths. Generally not used with +gem+.
21
25
  # * +labels+ is used to apply labels to matching code. This is really only useful when the package will be applied to
22
26
  # specific functions, via TargetMethods.
23
27
  # * +shallow+ indicates shallow mapping, in which only the entrypoint to a gem is recorded.
24
- Package = Struct.new(:path, :gem, :package_name, :exclude, :labels, :shallow) do
28
+ Package = Struct.new(:name, :path, :gem, :require_name, :exclude, :labels, :shallow, :builtin) do
25
29
  # This is for internal use only.
26
30
  private_methods :gem
27
31
 
@@ -43,20 +47,24 @@ module AppMap
43
47
  class << self
44
48
  # Builds a package for a path, such as `app/models` in a Rails app. Generally corresponds to a `path:` entry
45
49
  # in appmap.yml. Also used for mapping specific methods via TargetMethods.
46
- def build_from_path(path, shallow: false, package_name: nil, exclude: [], labels: [])
47
- Package.new(path, nil, package_name, exclude, labels, shallow)
50
+ def build_from_path(path, shallow: false, require_name: nil, exclude: [], labels: [])
51
+ Package.new(path, path, nil, require_name, exclude, labels, shallow)
52
+ end
53
+
54
+ def build_from_builtin(path, shallow: false, require_name: nil, exclude: [], labels: [])
55
+ Package.new(path, path, nil, require_name, exclude, labels, shallow, true)
48
56
  end
49
57
 
50
58
  # Builds a package for gem. Generally corresponds to a `gem:` entry in appmap.yml. Also used when mapping
51
59
  # a builtin.
52
- def build_from_gem(gem, shallow: true, package_name: nil, exclude: [], labels: [], optional: false, force: false)
60
+ def build_from_gem(gem, shallow: true, require_name: nil, exclude: [], labels: [], optional: false, force: false)
53
61
  if !force && %w[method_source activesupport].member?(gem)
54
62
  warn "WARNING: #{gem} cannot be AppMapped because it is a dependency of the appmap gem"
55
63
  return
56
64
  end
57
65
  path = gem_path(gem, optional)
58
66
  if path
59
- Package.new(path, gem, package_name, exclude, labels, shallow)
67
+ Package.new(gem, path, gem, require_name, exclude, labels, shallow)
60
68
  else
61
69
  AppMap::Util.startup_message "#{gem} is not available in the bundle"
62
70
  end
@@ -74,19 +82,16 @@ module AppMap
74
82
  end
75
83
  end
76
84
 
77
- def name
78
- gem || path
79
- end
80
-
81
85
  def to_h
82
86
  {
87
+ name: name,
83
88
  path: path,
84
- package_name: package_name,
85
89
  gem: gem,
86
- handler_class: handler_class.name,
90
+ require_name: require_name,
91
+ handler_class: handler_class ? handler_class.name : nil,
87
92
  exclude: Util.blank?(exclude) ? nil : exclude,
88
93
  labels: Util.blank?(labels) ? nil : labels,
89
- shallow: shallow
94
+ shallow: shallow.nil? ? nil : shallow,
90
95
  }.compact
91
96
  end
92
97
  end
@@ -96,12 +101,12 @@ module AppMap
96
101
  attr_reader :method_names, :package
97
102
 
98
103
  def initialize(method_names, package)
99
- @method_names = method_names
104
+ @method_names = Array(method_names).map(&:to_sym)
100
105
  @package = package
101
106
  end
102
107
 
103
108
  def include_method?(method_name)
104
- Array(method_names).include?(method_name)
109
+ method_names.include?(method_name.to_sym)
105
110
  end
106
111
 
107
112
  def to_h
@@ -110,6 +115,8 @@ module AppMap
110
115
  method_names: method_names
111
116
  }
112
117
  end
118
+
119
+ alias as_json to_h
113
120
  end
114
121
  private_constant :TargetMethods
115
122
 
@@ -117,11 +124,11 @@ module AppMap
117
124
  # entry in appmap.yml. When the Config is initialized, each Function is converted into
118
125
  # a Package and TargetMethods. It's called a Function rather than a Method, because Function
119
126
  # is the AppMap terminology.
120
- Function = Struct.new(:package, :cls, :labels, :function_names, :builtin, :package_name) do # :nodoc:
127
+ Function = Struct.new(:package, :cls, :labels, :function_names, :builtin, :require_name) do # :nodoc:
121
128
  def to_h
122
129
  {
123
130
  package: package,
124
- package_name: package_name,
131
+ require_name: require_name,
125
132
  class: cls,
126
133
  labels: labels,
127
134
  functions: function_names.map(&:to_sym),
@@ -138,9 +145,15 @@ module AppMap
138
145
  private_constant :MethodHook
139
146
 
140
147
  class << self
141
- def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
148
+ def package_hooks(methods, path: nil, gem: nil, force: false, builtin: false, handler_class: nil, require_name: nil)
142
149
  Array(methods).map do |method|
143
- package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
150
+ package = if builtin
151
+ Package.build_from_builtin(path, require_name: require_name, labels: method.labels, shallow: false)
152
+ elsif gem
153
+ Package.build_from_gem(gem, require_name: require_name, labels: method.labels, shallow: false, force: force, optional: true)
154
+ elsif path
155
+ Package.build_from_path(path, require_name: require_name, labels: method.labels, shallow: false)
156
+ end
144
157
  next unless package
145
158
 
146
159
  package.handler_class = handler_class if handler_class
@@ -151,87 +164,107 @@ module AppMap
151
164
  def method_hook(cls, method_names, labels)
152
165
  MethodHook.new(cls, method_names, labels)
153
166
  end
167
+
168
+ def declare_hook(hook_decl)
169
+ hook_decl = YAML.load(hook_decl) if hook_decl.is_a?(String)
170
+
171
+ methods_decl = hook_decl['methods'] || hook_decl['method']
172
+ methods_decl = Array(methods_decl) unless methods_decl.is_a?(Hash)
173
+ labels_decl = Array(hook_decl['labels'] || hook_decl['label'])
174
+
175
+ methods = methods_decl.map do |name|
176
+ class_name, method_name, static = name.include?('.') ? name.split('.', 2) + [ true ] : name.split('#', 2) + [ false ]
177
+ method_hook class_name, [ method_name ], labels_decl
178
+ end
179
+
180
+ require_name = hook_decl['require_name']
181
+ gem_name = hook_decl['gem']
182
+ path = hook_decl['path']
183
+ builtin = hook_decl['builtin']
184
+
185
+ options = {
186
+ builtin: builtin,
187
+ gem: gem_name,
188
+ path: path,
189
+ require_name: require_name || gem_name || path,
190
+ force: hook_decl['force']
191
+ }.compact
192
+
193
+ handler_class = hook_decl['handler_class']
194
+ options[:handler_class] = Util::class_from_string(handler_class) if handler_class
195
+
196
+ package_hooks(methods, **options)
197
+ end
198
+
199
+ def declare_hook_deprecated(hook_decl)
200
+ function_name = hook_decl['name']
201
+ package, cls, functions = []
202
+ if function_name
203
+ package, cls, _, function = Util.parse_function_name(function_name)
204
+ functions = Array(function)
205
+ else
206
+ package = hook_decl['package']
207
+ cls = hook_decl['class']
208
+ functions = hook_decl['function'] || hook_decl['functions']
209
+ raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
210
+ end
211
+
212
+ functions = Array(functions).map(&:to_sym)
213
+ labels = hook_decl['label'] || hook_decl['labels']
214
+ req = hook_decl['require']
215
+ builtin = hook_decl['builtin']
216
+
217
+ package_options = {}
218
+ package_options[:labels] = Array(labels).map(&:to_s) if labels if labels
219
+ package_options[:require_name] = req
220
+ package_options[:require_name] ||= package if builtin
221
+ tm = TargetMethods.new(functions, Package.build_from_path(package, **package_options))
222
+ ClassTargetMethods.new(cls, tm)
223
+ end
224
+
225
+ def builtin_hooks_path
226
+ [ [ __dir__, 'builtin_hooks' ].join('/') ] + ( ENV['APPMAP_BUILTIN_HOOKS_PATH'] || '').split(/[;:]/)
227
+ end
228
+
229
+ def gem_hooks_path
230
+ [ [ __dir__, 'gem_hooks' ].join('/') ] + ( ENV['APPMAP_GEM_HOOKS_PATH'] || '').split(/[;:]/)
231
+ end
232
+
233
+ def load_hooks
234
+ loader = lambda do |dir, &block|
235
+ basename = dir.split('/').compact.join('/')
236
+ [].tap do |hooks|
237
+ Dir.glob(Pathname.new(dir).join('**').join('*.yml').to_s).each do |yaml_file|
238
+ path = yaml_file[basename.length + 1...-4]
239
+ YAML.load(File.read(yaml_file)).map do |config|
240
+ block.call path, config
241
+ config
242
+ end.each do |config|
243
+ hooks << declare_hook(config)
244
+ end
245
+ end
246
+ end.compact
247
+ end
248
+
249
+ builtin_hooks = builtin_hooks_path.map do |path|
250
+ loader.(path) do |path, config|
251
+ config['path'] = path
252
+ config['builtin'] = true
253
+ end
254
+ end
255
+
256
+ gem_hooks = gem_hooks_path.map do |path|
257
+ loader.(path) do |path, config|
258
+ config['gem'] = path
259
+ config['builtin'] = false
260
+ end
261
+ end
262
+
263
+ (builtin_hooks + gem_hooks).flatten
264
+ end
154
265
  end
155
266
 
156
- # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
157
- # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
158
- # of appmap.yml.
159
- METHOD_HOOKS = [
160
- package_hooks('actionview',
161
- [
162
- method_hook('ActionView::Renderer', :render, %w[mvc.view]),
163
- method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
164
- method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
165
- ],
166
- handler_class: AppMap::Handler::Rails::Template::RenderHandler,
167
- package_name: 'action_view'
168
- ),
169
- package_hooks('actionview',
170
- [
171
- method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
172
- ],
173
- handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
174
- package_name: 'action_view'
175
- ),
176
- package_hooks('actionpack',
177
- [
178
- method_hook('ActionDispatch::Request::Session', %i[[] dig values fetch], %w[http.session.read]),
179
- method_hook('ActionDispatch::Request::Session', %i[destroy []= clear update delete merge], %w[http.session.write]),
180
- method_hook('ActionDispatch::Cookies::CookieJar', %i[[] fetch], %w[http.session.read]),
181
- method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session.write]),
182
- method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
183
- ],
184
- package_name: 'action_dispatch'
185
- ),
186
- package_hooks('cancancan',
187
- [
188
- method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
189
- method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
190
- ]
191
- ),
192
- package_hooks('actionpack',
193
- [
194
- method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
195
- ],
196
- package_name: 'action_controller'
197
- )
198
- ].flatten.freeze
199
-
200
- OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
201
-
202
- # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
203
- # Therefore, we can't rely on TracePoint to report the loading of this code.
204
- BUILTIN_HOOKS = {
205
- 'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
206
- 'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
207
- 'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
208
- 'OpenSSL::Cipher' => [
209
- TargetMethods.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
210
- TargetMethods.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
211
- ],
212
- 'ActiveSupport::Callbacks::CallbackSequence' => [
213
- TargetMethods.new(:invoke_before, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.before_action])),
214
- TargetMethods.new(:invoke_after, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.after_action])),
215
- ],
216
- 'ActiveSupport::SecurityUtils' => TargetMethods.new(:secure_compare, Package.build_from_gem('activesupport', force: true, package_name: 'active_support/security_utils', labels: %w[crypto.secure_compare])),
217
- 'OpenSSL::X509::Certificate' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
218
- 'Net::HTTP' => TargetMethods.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
219
- package.handler_class = AppMap::Handler::NetHTTP
220
- end),
221
- 'Net::SMTP' => TargetMethods.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
222
- 'Net::POP3' => TargetMethods.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
223
- # This is happening: Method send_command not found on Net::IMAP
224
- # 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
225
- # 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
226
- 'Psych' => [
227
- TargetMethods.new(%i[load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.parse])),
228
- TargetMethods.new(%i[dump dump_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml.generate])),
229
- ],
230
- 'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.parse])),
231
- 'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json.generate])),
232
- }.freeze
233
-
234
- attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :depends_config, :hooked_methods, :builtin_hooks
267
+ attr_reader :name, :appmap_dir, :packages, :exclude, :swagger_config, :depends_config, :gem_hooks, :builtin_hooks
235
268
 
236
269
  def initialize(name,
237
270
  packages: [],
@@ -246,31 +279,22 @@ module AppMap
246
279
  @depends_config = depends_config
247
280
  @hook_paths = Set.new(packages.map(&:path))
248
281
  @exclude = exclude
249
- @builtin_hooks = BUILTIN_HOOKS.dup
250
282
  @functions = functions
251
283
 
252
- @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
253
- hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
254
- end
255
-
256
- functions.each do |func|
257
- package_options = {}
258
- package_options[:labels] = func.labels if func.labels
259
- package_options[:package_name] = func.package_name
260
- package_options[:package_name] ||= func.package if func.builtin
261
- hook = TargetMethods.new(func.function_names, Package.build_from_path(func.package, **package_options))
262
- if func.builtin
263
- @builtin_hooks[func.cls] ||= []
264
- @builtin_hooks[func.cls] << hook
284
+ @builtin_hooks = Hash.new { |h,k| h[k] = [] }
285
+ @gem_hooks = Hash.new { |h,k| h[k] = [] }
286
+
287
+ (functions + self.class.load_hooks).each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, gem_hooks|
288
+ hooks = if cls_target_methods.target_methods.package.builtin
289
+ @builtin_hooks
265
290
  else
266
- @hooked_methods[func.cls] << hook
291
+ @gem_hooks
267
292
  end
293
+ hooks[cls_target_methods.cls] << cls_target_methods.target_methods
268
294
  end
269
295
 
270
- @hooked_methods.each_value do |hooks|
271
- Array(hooks).each do |hook|
272
- @hook_paths << hook.package.path
273
- end
296
+ @gem_hooks.each_value do |hooks|
297
+ @hook_paths += Array(hooks).map { |hook| hook.package.path }.compact
274
298
  end
275
299
  end
276
300
 
@@ -334,24 +358,15 @@ module AppMap
334
358
  }.compact
335
359
 
336
360
  if config_data['functions']
337
- config_params[:functions] = config_data['functions'].map do |function_data|
338
- function_name = function_data['name']
339
- package, cls, functions = []
340
- if function_name
341
- package, cls, _, function = Util.parse_function_name(function_name)
342
- functions = Array(function)
361
+ config_params[:functions] = config_data['functions'].map do |hook_decl|
362
+ if hook_decl['name'] || hook_decl['package']
363
+ declare_hook_deprecated(hook_decl)
343
364
  else
344
- package = function_data['package']
345
- cls = function_data['class']
346
- functions = function_data['function'] || function_data['functions']
347
- raise %q(AppMap config 'function' element should specify 'package', 'class' and 'function' or 'functions') unless package && cls && functions
365
+ # Support the same syntax within the 'functions' that's used for externalized
366
+ # hook config.
367
+ declare_hook(hook_decl)
348
368
  end
349
-
350
- functions = Array(functions).map(&:to_sym)
351
- labels = function_data['label'] || function_data['labels']
352
- labels = Array(labels).map(&:to_s) if labels
353
- Function.new(package, cls, labels, functions, function_data['builtin'], function_data['require'])
354
- end
369
+ end.flatten
355
370
  end
356
371
 
357
372
  config_params[:packages] = \
@@ -366,7 +381,11 @@ module AppMap
366
381
  shallow = package['shallow']
367
382
  # shallow is true by default for gems
368
383
  shallow = true if shallow.nil?
369
- Package.build_from_gem(gem, package_name: package['package'], exclude: package['exclude'] || [], shallow: shallow)
384
+
385
+ require_name = \
386
+ package['package'] || #deprecated
387
+ package['require_name']
388
+ Package.build_from_gem(gem, require_name: require_name, exclude: package['exclude'] || [], shallow: shallow)
370
389
  else
371
390
  Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
372
391
  end
@@ -417,8 +436,8 @@ module AppMap
417
436
 
418
437
  # Hook a method which is specified by class and method name.
419
438
  def package_for_code_object
420
- Array(config.hooked_methods[cls.name])
421
- .compact
439
+ class_name = cls.to_s.index('#<Class:') == 0 ? cls.to_s['#<Class:'.length...-1] : cls.name
440
+ Array(config.gem_hooks[class_name])
422
441
  .find { |hook| hook.include_method?(method.name) }
423
442
  &.package
424
443
  end
data/lib/appmap/event.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'weakref'
4
+
3
5
  module AppMap
4
6
  module Event
5
7
  @@id_counter = 0
@@ -111,10 +113,12 @@ module AppMap
111
113
  rescue NoMethodError
112
114
  begin
113
115
  value.inspect
114
- rescue StandardError
116
+ rescue
115
117
  last_resort_string.call
116
118
  end
117
- rescue StandardError
119
+ rescue WeakRef::RefError
120
+ nil
121
+ rescue
118
122
  last_resort_string.call
119
123
  end
120
124
  end
@@ -0,0 +1,40 @@
1
+ - methods:
2
+ - ActionDispatch::Request::Session#[]
3
+ - ActionDispatch::Request::Session#dig
4
+ - ActionDispatch::Request::Session#values
5
+ - ActionDispatch::Request::Session#fetch
6
+ - ActionDispatch::Cookies::CookieJar#[]
7
+ - ActionDispatch::Cookies::CookieJar#fetch
8
+ label: http.session.read
9
+ require_name: action_dispatch
10
+ - methods:
11
+ - ActionDispatch::Request::Session#destroy
12
+ - ActionDispatch::Request::Session#[]=
13
+ - ActionDispatch::Request::Session#clear
14
+ - ActionDispatch::Request::Session#update
15
+ - ActionDispatch::Request::Session#delete
16
+ - ActionDispatch::Request::Session#merge
17
+ - ActionDispatch::Cookies::CookieJar#[]=
18
+ - ActionDispatch::Cookies::CookieJar#clear
19
+ - ActionDispatch::Cookies::CookieJar#update
20
+ - ActionDispatch::Cookies::CookieJar#delete
21
+ - ActionDispatch::Cookies::CookieJar#recycle!
22
+ label: http.session.write
23
+ require_name: action_dispatch
24
+ - methods:
25
+ - ActionDispatch::Cookies::EncryptedCookieJar#[]=
26
+ - ActionDispatch::Cookies::EncryptedCookieJar#clear
27
+ - ActionDispatch::Cookies::EncryptedCookieJar#update
28
+ - ActionDispatch::Cookies::EncryptedCookieJar#delete
29
+ - ActionDispatch::Cookies::EncryptedCookieJar#recycle
30
+ labels:
31
+ - http.cookie
32
+ - crypto.encrypt
33
+ require_name: action_dispatch
34
+ - methods:
35
+ - ActionController::Instrumentation#process_action
36
+ - ActionController::Instrumentation#send_file
37
+ - ActionController::Instrumentation#send_data
38
+ - ActionController::Instrumentation#redirect_to
39
+ label: mvc.controller
40
+ require_name: action_controller
@@ -0,0 +1,13 @@
1
+ - methods:
2
+ - ActionView::Renderer#render
3
+ - ActionView::TemplateRenderer#render
4
+ - ActionView::PartialRenderer#render
5
+ label: mvc.view
6
+ handler_class: AppMap::Handler::Rails::Template::RenderHandler
7
+ require_name: action_view
8
+ - methods:
9
+ - ActionView::Resolver#find_all
10
+ - ActionView::Resolver#find_all_anywhere
11
+ label: mvc.template.resolver
12
+ handler_class: AppMap::Handler::Rails::Template::ResolverHandler
13
+ require_name: action_view
@@ -0,0 +1,12 @@
1
+ - method: ActiveSupport::Callbacks::CallbackSequence#invoke_before
2
+ label: mvc.before_action
3
+ require_name: active_support
4
+ force: true
5
+ - method: ActiveSupport::Callbacks::CallbackSequence#invoke_after
6
+ label: mvc.after_action
7
+ require_name: active_support
8
+ force: true
9
+ - method: ActiveSupport::SecurityUtils#secure_compare
10
+ label: crypto.secure_compare
11
+ require_name: active_support/security_utils
12
+ force: true
@@ -0,0 +1,6 @@
1
+ - methods:
2
+ - CanCan::ControllerAdditions#authorize!
3
+ - CanCan::ControllerAdditions#can?
4
+ - CanCan::ControllerAdditions#cannot?
5
+ - CanCan::Ability#authorize?
6
+ label: security.authorization