appmap 0.66.2 → 0.68.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: 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