appmap 0.77.2 → 0.78.0
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 +4 -4
- data/.rubocop.yml +1 -0
- data/.travis.yml +0 -7
- data/CHANGELOG.md +21 -0
- data/Rakefile +29 -3
- data/ext/appmap/appmap.c +21 -2
- data/lib/appmap/builtin_hooks/ruby.yml +6 -3
- data/lib/appmap/config.rb +9 -1
- data/lib/appmap/handler/eval.rb +41 -0
- data/lib/appmap/handler/function.rb +8 -8
- data/lib/appmap/handler/net_http.rb +19 -18
- data/lib/appmap/handler/rails/request_handler.rb +3 -4
- data/lib/appmap/handler/rails/template.rb +68 -62
- data/lib/appmap/hook/method/ruby2.rb +56 -0
- data/lib/appmap/hook/method/ruby3.rb +56 -0
- data/lib/appmap/hook/method.rb +42 -98
- data/lib/appmap/hook.rb +2 -2
- data/lib/appmap/version.rb +1 -1
- data/spec/fixtures/rails7_users_app/.dockerignore +1 -0
- data/spec/fixtures/rails7_users_app/.gitattributes +7 -0
- data/spec/fixtures/rails7_users_app/.gitignore +31 -0
- data/spec/fixtures/rails7_users_app/.rspec +1 -0
- data/spec/fixtures/rails7_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails7_users_app/Dockerfile +30 -0
- data/spec/fixtures/rails7_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails7_users_app/Gemfile +99 -0
- data/spec/fixtures/rails7_users_app/README.md +24 -0
- data/spec/fixtures/rails7_users_app/Rakefile +6 -0
- data/spec/fixtures/rails7_users_app/app/assets/config/manifest.js +4 -0
- data/spec/fixtures/rails7_users_app/app/assets/images/.keep +0 -0
- data/spec/fixtures/rails7_users_app/app/assets/stylesheets/application.css +15 -0
- data/spec/fixtures/rails7_users_app/app/channels/application_cable/channel.rb +4 -0
- data/spec/fixtures/rails7_users_app/app/channels/application_cable/connection.rb +4 -0
- data/spec/fixtures/rails7_users_app/app/controllers/application_controller.rb +2 -0
- data/spec/fixtures/rails7_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails7_users_app/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails7_users_app/app/javascript/application.js +3 -0
- data/spec/fixtures/rails7_users_app/app/javascript/controllers/application.js +9 -0
- data/spec/fixtures/rails7_users_app/app/javascript/controllers/hello_controller.js +7 -0
- data/spec/fixtures/rails7_users_app/app/javascript/controllers/index.js +11 -0
- data/spec/fixtures/rails7_users_app/app/jobs/application_job.rb +7 -0
- data/spec/fixtures/rails7_users_app/app/mailers/application_mailer.rb +4 -0
- data/spec/fixtures/rails7_users_app/app/models/application_record.rb +3 -0
- data/spec/fixtures/rails7_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails7_users_app/app/models/instance.rb +7 -0
- data/spec/fixtures/rails7_users_app/app/models/instructor.rb +7 -0
- data/spec/fixtures/rails7_users_app/app/views/layouts/application.html.erb +16 -0
- data/spec/fixtures/rails7_users_app/app/views/layouts/mailer.html.erb +13 -0
- data/spec/fixtures/rails7_users_app/app/views/layouts/mailer.text.erb +1 -0
- data/spec/fixtures/rails7_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails7_users_app/bin/bundle +114 -0
- data/spec/fixtures/rails7_users_app/bin/importmap +4 -0
- data/spec/fixtures/rails7_users_app/bin/rails +4 -0
- data/spec/fixtures/rails7_users_app/bin/rake +4 -0
- data/spec/fixtures/rails7_users_app/bin/setup +33 -0
- data/spec/fixtures/rails7_users_app/config/application.rb +22 -0
- data/spec/fixtures/rails7_users_app/config/boot.rb +4 -0
- data/spec/fixtures/rails7_users_app/config/cable.yml +10 -0
- data/spec/fixtures/rails7_users_app/config/credentials.yml.enc +1 -0
- data/spec/fixtures/rails7_users_app/config/database.yml +86 -0
- data/spec/fixtures/rails7_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails7_users_app/config/environments/development.rb +70 -0
- data/spec/fixtures/rails7_users_app/config/environments/production.rb +93 -0
- data/spec/fixtures/rails7_users_app/config/environments/test.rb +60 -0
- data/spec/fixtures/rails7_users_app/config/importmap.rb +7 -0
- data/spec/fixtures/rails7_users_app/config/initializers/assets.rb +12 -0
- data/spec/fixtures/rails7_users_app/config/initializers/content_security_policy.rb +26 -0
- data/spec/fixtures/rails7_users_app/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/fixtures/rails7_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails7_users_app/config/initializers/permissions_policy.rb +11 -0
- data/spec/fixtures/rails7_users_app/config/locales/en.yml +33 -0
- data/spec/fixtures/rails7_users_app/config/puma.rb +43 -0
- data/spec/fixtures/rails7_users_app/config/routes.rb +6 -0
- data/spec/fixtures/rails7_users_app/config/storage.yml +34 -0
- data/spec/fixtures/rails7_users_app/config.ru +6 -0
- data/spec/fixtures/rails7_users_app/create_app +31 -0
- data/spec/fixtures/rails7_users_app/db/migrate/20220328093141_create_instances.rb +8 -0
- data/spec/fixtures/rails7_users_app/db/migrate/20220328093154_create_instructors.rb +8 -0
- data/spec/fixtures/rails7_users_app/db/schema.rb +27 -0
- data/spec/fixtures/rails7_users_app/db/seeds.rb +7 -0
- data/spec/fixtures/rails7_users_app/docker-compose.yml +31 -0
- data/spec/fixtures/rails7_users_app/lib/assets/.keep +0 -0
- data/spec/fixtures/rails7_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails7_users_app/log/.keep +0 -0
- data/spec/fixtures/rails7_users_app/public/404.html +67 -0
- data/spec/fixtures/rails7_users_app/public/422.html +67 -0
- data/spec/fixtures/rails7_users_app/public/500.html +66 -0
- data/spec/fixtures/rails7_users_app/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/fixtures/rails7_users_app/public/apple-touch-icon.png +0 -0
- data/spec/fixtures/rails7_users_app/public/favicon.ico +0 -0
- data/spec/fixtures/rails7_users_app/public/robots.txt +1 -0
- data/spec/fixtures/rails7_users_app/storage/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/application_system_test_case.rb +5 -0
- data/spec/fixtures/rails7_users_app/test/channels/application_cable/connection_test.rb +11 -0
- data/spec/fixtures/rails7_users_app/test/controllers/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/fixtures/files/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/fixtures/instances.yml +11 -0
- data/spec/fixtures/rails7_users_app/test/fixtures/instructors.yml +11 -0
- data/spec/fixtures/rails7_users_app/test/helpers/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/integration/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/mailers/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/models/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/models/instance_test.rb +6 -0
- data/spec/fixtures/rails7_users_app/test/models/instructor_test.rb +7 -0
- data/spec/fixtures/rails7_users_app/test/system/.keep +0 -0
- data/spec/fixtures/rails7_users_app/test/test_helper.rb +13 -0
- data/spec/handler/eval_spec.rb +66 -0
- data/spec/hook_spec.rb +2 -2
- data/spec/rails_recording_spec.rb +6 -4
- data/spec/rails_spec_helper.rb +7 -0
- data/spec/rails_test_spec.rb +45 -0
- metadata +95 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8dce970e998105e2e322410b204af7a0ff48790cd177225468e9b49c75651fdb
|
|
4
|
+
data.tar.gz: d93b8f4f0690bf5ade69b2c08e4354d3043567fc4bf6018d6e68084eece08be9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bea7fecb6bbae7feb68fa649ecba6276b3d497312ff99054c527d2f74117b584dd7119fa8e4a346b0091089786e90a0a2f2f375f1bc94a0e5437dfa8cff99e20
|
|
7
|
+
data.tar.gz: 913ab70d04467c584ecf5edfe4c8d60807974cf3d01eb9f2107ceaaa8677c157dc3b0e4130b6e90dcdaba3ba055b4a5268b40e94d0054eb6c41d1146205b0eff
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [0.78.0](https://github.com/applandinc/appmap-ruby/compare/v0.77.4...v0.78.0) (2022-04-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Hook and label Kernel#eval ([e0c151d](https://github.com/applandinc/appmap-ruby/commit/e0c151d1371f5bed5597ffd0d3bfebb8bca247c2))
|
|
7
|
+
|
|
8
|
+
## [0.77.4](https://github.com/applandinc/appmap-ruby/compare/v0.77.3...v0.77.4) (2022-04-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* Update Rails request handler to the new hook architecture ([595b39a](https://github.com/applandinc/appmap-ruby/commit/595b39abb030c1dcf85c83e4717c25d4c5177d4d))
|
|
14
|
+
|
|
15
|
+
## [0.77.3](https://github.com/applandinc/appmap-ruby/compare/v0.77.2...v0.77.3) (2022-03-29)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* Rescue exceptions when calling Class#to_s ([f59f2f6](https://github.com/applandinc/appmap-ruby/commit/f59f2f6b39664ff050486c88ff1b859ca0db48d8))
|
|
21
|
+
|
|
1
22
|
## [0.77.2](https://github.com/applandinc/appmap-ruby/compare/v0.77.1...v0.77.2) (2022-03-25)
|
|
2
23
|
|
|
3
24
|
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,21 @@ $: << File.join(__dir__, 'lib')
|
|
|
2
2
|
require 'appmap/version'
|
|
3
3
|
GEM_VERSION = AppMap::VERSION
|
|
4
4
|
|
|
5
|
+
# Make sure the local version is not behind the one on
|
|
6
|
+
# rubygems.org (it's ok if they're the same).
|
|
7
|
+
#
|
|
8
|
+
# If it is behind, the fixture images won't get updated with the gem
|
|
9
|
+
# built from the local source, so you'll wind up testing the rubygems
|
|
10
|
+
# version instead.
|
|
11
|
+
unless ENV['SKIP_VERSION_CHECK']
|
|
12
|
+
require 'json'
|
|
13
|
+
require 'net/http'
|
|
14
|
+
rubygems_version = JSON.parse(Net::HTTP.get(URI.parse('https://rubygems.org/api/v1/gems/appmap.json')))['version']
|
|
15
|
+
if Gem::Version.new(GEM_VERSION) < Gem::Version.new(rubygems_version)
|
|
16
|
+
raise "#{GEM_VERSION} < #{rubygems_version}. Rebase to avoid build issues."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
5
20
|
require 'rake/testtask'
|
|
6
21
|
require 'rdoc/task'
|
|
7
22
|
|
|
@@ -24,7 +39,7 @@ RUBY_VERSIONS=%w[2.6 2.7 3.0 3.1].select do |version|
|
|
|
24
39
|
|
|
25
40
|
false
|
|
26
41
|
end
|
|
27
|
-
FIXTURE_APPS
|
|
42
|
+
FIXTURE_APPS=[:rack_users_app, :rails6_users_app, :rails5_users_app, :rails7_users_app => {:ruby_version => '>= 2.7'}]
|
|
28
43
|
|
|
29
44
|
def run_cmd(*cmd)
|
|
30
45
|
$stderr.puts "Running: #{cmd}"
|
|
@@ -83,7 +98,17 @@ namespace :build do
|
|
|
83
98
|
RUBY_VERSIONS.each do |ruby_version|
|
|
84
99
|
namespace ruby_version do
|
|
85
100
|
desc "build:fixtures:#{ruby_version}"
|
|
86
|
-
FIXTURE_APPS.each do |
|
|
101
|
+
FIXTURE_APPS.each do |app_spec|
|
|
102
|
+
app = if app_spec.instance_of?(Hash)
|
|
103
|
+
app_spec = app_spec.flatten
|
|
104
|
+
version_rqt = Gem::Requirement.create(app_spec[1][:ruby_version])
|
|
105
|
+
next unless version_rqt =~ (Gem::Version.new(ruby_version))
|
|
106
|
+
app = app_spec[0]
|
|
107
|
+
else
|
|
108
|
+
app = app_spec
|
|
109
|
+
end.to_s
|
|
110
|
+
|
|
111
|
+
|
|
87
112
|
desc app
|
|
88
113
|
task app => ["base:#{ruby_version}"] do
|
|
89
114
|
build_app_image(app, ruby_version)
|
|
@@ -109,12 +134,13 @@ def run_specs(ruby_version, task_args)
|
|
|
109
134
|
# description), because it's not intended to be invoked directly
|
|
110
135
|
RSpec::Core::RakeTask.new("rspec_#{ruby_version}", [:specs]) do |task, args|
|
|
111
136
|
task.exclude_pattern = 'spec/fixtures/**/*_spec.rb'
|
|
137
|
+
task.rspec_opts = '-f doc'
|
|
112
138
|
if args.count > 0
|
|
113
139
|
# There doesn't appear to be a value for +pattern+ that will
|
|
114
140
|
# cause it to be ignored. Setting it to '' or +nil+ causes an
|
|
115
141
|
# empty argument to get passed to rspec, which confuses it.
|
|
116
142
|
task.pattern = 'never match this'
|
|
117
|
-
task.rspec_opts
|
|
143
|
+
task.rspec_opts += " " + args.to_a.join(' ')
|
|
118
144
|
end
|
|
119
145
|
end
|
|
120
146
|
|
data/ext/appmap/appmap.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
+
#include <ruby/debug.h>
|
|
2
3
|
#include <ruby/intern.h>
|
|
3
4
|
|
|
4
5
|
// Seems like CLASS_OR_MODULE_P should really be in a header file in
|
|
@@ -39,7 +40,7 @@ am_define_method_with_arity(VALUE mod, VALUE name, VALUE arity, VALUE proc)
|
|
|
39
40
|
{
|
|
40
41
|
VALUE arities_key = rb_intern(ARITIES_KEY);
|
|
41
42
|
VALUE arities = rb_ivar_get(mod, arities_key);
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
if (arities == Qundef || NIL_P(arities)) {
|
|
44
45
|
arities = rb_hash_new();
|
|
45
46
|
rb_ivar_set(mod, arities_key, arities);
|
|
@@ -62,7 +63,7 @@ am_get_method_arity(VALUE method, VALUE orig_arity_method)
|
|
|
62
63
|
}
|
|
63
64
|
// Didn't find one, call the original method.
|
|
64
65
|
if (NIL_P(arity)) {
|
|
65
|
-
VALUE bound_method = rb_funcall(orig_arity_method, rb_intern("bind"), 1, method);
|
|
66
|
+
VALUE bound_method = rb_funcall(orig_arity_method, rb_intern("bind"), 1, method);
|
|
66
67
|
arity = rb_funcall(bound_method, rb_intern("call"), 0);
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -83,11 +84,29 @@ am_method_arity(VALUE method)
|
|
|
83
84
|
return am_get_method_arity(method, orig_method_arity);
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
static VALUE
|
|
88
|
+
bindings_callback(const rb_debug_inspector_t *dbg_context, void *level)
|
|
89
|
+
{
|
|
90
|
+
const int i = NUM2INT((VALUE) level);
|
|
91
|
+
if (i >= RARRAY_LEN(rb_debug_inspector_backtrace_locations(dbg_context))) {
|
|
92
|
+
return Qnil;
|
|
93
|
+
} else {
|
|
94
|
+
return rb_debug_inspector_frame_binding_get(dbg_context, i);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static VALUE
|
|
99
|
+
am_previous_bindings(VALUE self, VALUE level)
|
|
100
|
+
{
|
|
101
|
+
return rb_debug_inspector_open(bindings_callback, (void *) level);
|
|
102
|
+
}
|
|
103
|
+
|
|
86
104
|
void Init_appmap() {
|
|
87
105
|
VALUE appmap = rb_define_module("AppMap");
|
|
88
106
|
am_AppMapHook = rb_define_class_under(appmap, "Hook", rb_cObject);
|
|
89
107
|
|
|
90
108
|
rb_define_singleton_method(am_AppMapHook, "singleton_method_owner_name", singleton_method_owner_name, 1);
|
|
109
|
+
rb_define_module_function(appmap, "caller_binding", am_previous_bindings, 1);
|
|
91
110
|
|
|
92
111
|
rb_define_method(rb_cModule, "define_method_with_arity", am_define_method_with_arity, 3);
|
|
93
112
|
rb_define_method(rb_cUnboundMethod, "arity", am_unbound_method_arity, 0);
|
|
@@ -14,14 +14,17 @@
|
|
|
14
14
|
- String#unpack1
|
|
15
15
|
require_name: ruby
|
|
16
16
|
label: string.unpack
|
|
17
|
-
|
|
17
|
+
- methods:
|
|
18
|
+
- Kernel#eval
|
|
19
|
+
require_name: ruby
|
|
20
|
+
label: lang.eval
|
|
21
|
+
handler_class: AppMap::Handler::Eval
|
|
18
22
|
# TODO: eval does not happen in the right context, and therefore any new constants
|
|
19
23
|
# which are defined are placed on the wrong module/class.
|
|
20
|
-
# - Kernel#eval
|
|
21
24
|
# - Binding#eval
|
|
22
25
|
# - BasicObject#instance_eval
|
|
23
26
|
# These methods cannot be hooked as far as I can tell.
|
|
24
|
-
# Why? When calling one of these functions, the context at the point of
|
|
27
|
+
# Why? When calling one of these functions, the context at the point of
|
|
25
28
|
# definition is used. It's not possible to bind class_eval to a new context.
|
|
26
29
|
# - Module#class_eval
|
|
27
30
|
# - Module#module_eval
|
data/lib/appmap/config.rb
CHANGED
|
@@ -450,7 +450,15 @@ module AppMap
|
|
|
450
450
|
|
|
451
451
|
# Hook a method which is specified by class and method name.
|
|
452
452
|
def package_for_code_object
|
|
453
|
-
class_name =
|
|
453
|
+
class_name = begin
|
|
454
|
+
cls.to_s.index('#<Class:') == 0 ? cls.to_s['#<Class:'.length...-1] : cls.name
|
|
455
|
+
rescue
|
|
456
|
+
# Calling #to_s on some Rails classes
|
|
457
|
+
# (e.g. those generated to represent
|
|
458
|
+
# associations) will raise an exception. Fall
|
|
459
|
+
# back to using the class name.
|
|
460
|
+
cls.name
|
|
461
|
+
end
|
|
454
462
|
Array(config.gem_hooks[class_name])
|
|
455
463
|
.find { |hook| hook.include_method?(method.name) }
|
|
456
464
|
&.package
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'appmap/handler/function'
|
|
4
|
+
|
|
5
|
+
module AppMap
|
|
6
|
+
module Handler
|
|
7
|
+
# Handler class for Kernel#eval.
|
|
8
|
+
#
|
|
9
|
+
# Second argument to eval is a Binding, which despite the name (and
|
|
10
|
+
# the accessible methods) in addition to locals and receiver also
|
|
11
|
+
# encapsulates the entire execution context, in particular including
|
|
12
|
+
# the lexical scope. This is especially important for constant lookup
|
|
13
|
+
# and definition.
|
|
14
|
+
#
|
|
15
|
+
# If the binding is not provided, by default eval will run in the
|
|
16
|
+
# current frame. Since we call it here, this will mean the #do_call
|
|
17
|
+
# frame, which would make AppMap::Handler::Eval the lexical scope
|
|
18
|
+
# for constant lookup and definition; as a consequence
|
|
19
|
+
# eg. `eval "class Foo; end"` would define
|
|
20
|
+
# AppMap::Handler::Eval::Foo instead of defining it in
|
|
21
|
+
# the module where the original call was made.
|
|
22
|
+
#
|
|
23
|
+
# To avoid this, we explicitly substitute the correct execution
|
|
24
|
+
# context, up several stack frames.
|
|
25
|
+
class Eval < Function
|
|
26
|
+
# The depth of the frame we need to pluck out:
|
|
27
|
+
# 1. Hook::Method#do_call
|
|
28
|
+
# 2. Hook::Method#trace_call
|
|
29
|
+
# 3. Hook::Method#call
|
|
30
|
+
# 4. proc generated by Hook::Method#hook_method_def
|
|
31
|
+
# 5. the (intended) frame of the original eval that we hooked
|
|
32
|
+
# Note it needs to be adjusted if this call sequence changes.
|
|
33
|
+
FRAME_DEPTH = 5
|
|
34
|
+
|
|
35
|
+
def do_call(receiver, src = nil, context = nil, *rest)
|
|
36
|
+
context ||= AppMap.caller_binding FRAME_DEPTH
|
|
37
|
+
hook_method.bind(receiver).call(src, context, *rest)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'appmap/event'
|
|
4
|
+
require 'appmap/hook/method'
|
|
4
5
|
|
|
5
6
|
module AppMap
|
|
6
7
|
module Handler
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
# Base handler class, will emit method call and return events.
|
|
9
|
+
class Function < Hook::Method
|
|
10
|
+
def handle_call(receiver, args)
|
|
11
|
+
AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
|
12
|
+
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
14
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
|
15
|
+
AppMap::Event::MethodReturn.build_from_invocation(call_event_id, return_value, exception, elapsed: elapsed)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'appmap/event'
|
|
4
|
+
require 'appmap/hook/method'
|
|
4
5
|
require 'appmap/util'
|
|
5
6
|
require 'rack'
|
|
6
7
|
|
|
@@ -81,29 +82,29 @@ module AppMap
|
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
class
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
# Handler class for HTTP requests.
|
|
86
|
+
# Emits HTTP request events instead of method calls.
|
|
87
|
+
class NetHTTP < Hook::Method
|
|
88
|
+
def self.copy_headers(obj)
|
|
89
|
+
{}.tap do |headers|
|
|
90
|
+
obj.each_header do |key, value|
|
|
91
|
+
key = key.split('-').map(&:capitalize).join('-')
|
|
92
|
+
headers[key] = value
|
|
92
93
|
end
|
|
93
94
|
end
|
|
95
|
+
end
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
def handle_call(receiver, args)
|
|
98
|
+
# request will call itself again in a start block if it's not already started.
|
|
99
|
+
return unless receiver.started?
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
http = receiver
|
|
102
|
+
request = args.first
|
|
103
|
+
HTTPClientRequest.new(http, request)
|
|
104
|
+
end
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
106
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
|
107
|
+
HTTPClientResponse.new(return_value, call_event_id, elapsed)
|
|
107
108
|
end
|
|
108
109
|
end
|
|
109
110
|
end
|
|
@@ -100,15 +100,14 @@ module AppMap
|
|
|
100
100
|
|
|
101
101
|
protected
|
|
102
102
|
|
|
103
|
-
def before_hook(receiver,
|
|
103
|
+
def before_hook(receiver, *)
|
|
104
104
|
call_event = HTTPServerRequest.new(receiver.request)
|
|
105
105
|
# http_server_request events are i/o and do not require a package name.
|
|
106
106
|
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
|
107
|
-
|
|
107
|
+
call_event
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
def after_hook(receiver, call_event,
|
|
111
|
-
elapsed = TIME_NOW.call - start_time
|
|
110
|
+
def after_hook(receiver, call_event, elapsed, *)
|
|
112
111
|
return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
|
|
113
112
|
AppMap.tracing.record_event return_event
|
|
114
113
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'appmap/handler/function'
|
|
3
4
|
require 'appmap/event'
|
|
4
5
|
|
|
5
6
|
module AppMap
|
|
@@ -9,7 +10,7 @@ module AppMap
|
|
|
9
10
|
LOG = (ENV['APPMAP_TEMPLATE_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
|
10
11
|
|
|
11
12
|
# All the code which is touched by the AppMap is recorded in the classMap.
|
|
12
|
-
# This duck-typed 'method' is used to represent a view template as a package,
|
|
13
|
+
# This duck-typed 'method' is used to represent a view template as a package,
|
|
13
14
|
# class, and method in the classMap.
|
|
14
15
|
# The class name is generated from the template path. The package name is
|
|
15
16
|
# 'app/views', and the method name is 'render'. The source location of the method
|
|
@@ -41,31 +42,31 @@ module AppMap
|
|
|
41
42
|
def package
|
|
42
43
|
'app/views'
|
|
43
44
|
end
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
def name
|
|
46
47
|
'render'
|
|
47
48
|
end
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
def source_location
|
|
50
51
|
path
|
|
51
52
|
end
|
|
52
|
-
|
|
53
|
+
|
|
53
54
|
def static
|
|
54
55
|
true
|
|
55
56
|
end
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
def comment
|
|
58
59
|
nil
|
|
59
60
|
end
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
def labels
|
|
62
63
|
[ 'mvc.template' ]
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
# TemplateCall is a type of function call which is specialized to view template rendering. Since
|
|
67
68
|
# there isn't really a perfect method in Rails to hook, this one is synthesized from the available
|
|
68
|
-
# information.
|
|
69
|
+
# information.
|
|
69
70
|
class TemplateCall < AppMap::Event::MethodEvent
|
|
70
71
|
# This is basically the +self+ parameter.
|
|
71
72
|
attr_reader :render_instance
|
|
@@ -75,19 +76,19 @@ module AppMap
|
|
|
75
76
|
attr_accessor :ready
|
|
76
77
|
|
|
77
78
|
alias ready? ready
|
|
78
|
-
|
|
79
|
+
|
|
79
80
|
def initialize(render_instance)
|
|
80
81
|
super :call
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
AppMap::Event::MethodEvent.build_from_invocation(:call, event: self)
|
|
83
84
|
@ready = false
|
|
84
85
|
@render_instance = render_instance
|
|
85
86
|
end
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
def static?
|
|
88
89
|
true
|
|
89
90
|
end
|
|
90
|
-
|
|
91
|
+
|
|
91
92
|
def to_h
|
|
92
93
|
super.tap do |h|
|
|
93
94
|
h[:defined_class] = path ? path.parameterize.underscore : 'inline_template'
|
|
@@ -103,71 +104,76 @@ module AppMap
|
|
|
103
104
|
end.compact
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
TEMPLATE_RENDERER = 'appmap.handler.rails.template.renderer'
|
|
108
109
|
|
|
109
110
|
# Hooks the ActionView::Resolver methods +find_all+, +find_all_anywhere+. The resolver is used
|
|
110
111
|
# during template rendering to lookup the template file path from parameters such as the
|
|
111
112
|
# template name, prefix, and partial (boolean).
|
|
112
|
-
class ResolverHandler
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
name, prefix, partial = args
|
|
117
|
-
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
|
118
|
-
|
|
119
|
-
AppMap::Handler::Function.handle_call(defined_class, hook_method, receiver, args)
|
|
120
|
-
end
|
|
113
|
+
class ResolverHandler < AppMap::Handler::Function
|
|
114
|
+
def handle_call(receiver, args)
|
|
115
|
+
name, prefix, partial = args
|
|
116
|
+
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# template will be recorded in the classMap.
|
|
125
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
|
126
|
-
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
|
127
|
-
path_obj = Array(return_value).first
|
|
128
|
-
|
|
129
|
-
warn "Resolver return: #{path_obj}" if LOG
|
|
130
|
-
|
|
131
|
-
if path_obj
|
|
132
|
-
path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
|
133
|
-
path_obj.identifier
|
|
134
|
-
else
|
|
135
|
-
path_obj.inspect
|
|
136
|
-
end
|
|
137
|
-
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
|
138
|
-
AppMap.tracing.record_method(TemplateMethod.new(path))
|
|
139
|
-
renderer.path ||= path if renderer
|
|
140
|
-
end
|
|
118
|
+
super
|
|
119
|
+
end
|
|
141
120
|
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
# When the resolver returns, look to see if there is template rendering underway.
|
|
122
|
+
# If so, populate the template path. In all cases, add a TemplateMethod so that the
|
|
123
|
+
# template will be recorded in the classMap.
|
|
124
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
|
125
|
+
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
|
126
|
+
path_obj = Array(return_value).first
|
|
127
|
+
|
|
128
|
+
warn "Resolver return: #{path_obj}" if LOG
|
|
129
|
+
|
|
130
|
+
record_template_path renderer, path_obj
|
|
131
|
+
|
|
132
|
+
super
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def record_template_path(renderer, path_obj)
|
|
136
|
+
return unless path_obj
|
|
137
|
+
|
|
138
|
+
path = path_from_obj path_obj
|
|
139
|
+
AppMap.tracing.record_method(TemplateMethod.new(path))
|
|
140
|
+
renderer.path ||= path if renderer
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def path_from_obj(path_obj)
|
|
144
|
+
path =
|
|
145
|
+
if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
|
146
|
+
path_obj.identifier
|
|
147
|
+
else
|
|
148
|
+
path_obj.inspect
|
|
149
|
+
end
|
|
150
|
+
path = path[Dir.pwd.length + 1..] if path.index(Dir.pwd) == 0
|
|
151
|
+
path
|
|
144
152
|
end
|
|
145
153
|
end
|
|
146
154
|
|
|
147
155
|
# Hooks the ActionView::Renderer method +render+. This method is used by Rails to perform
|
|
148
156
|
# template rendering. The TemplateCall event which is emitted by this handler has a
|
|
149
|
-
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
|
150
|
-
class RenderHandler
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
warn "Renderer: #{options}" if LOG
|
|
157
|
-
|
|
158
|
-
TemplateCall.new(receiver).tap do |call|
|
|
159
|
-
Thread.current[TEMPLATE_RENDERER] ||= []
|
|
160
|
-
Thread.current[TEMPLATE_RENDERER] << call
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def handle_return(call_event_id, elapsed, return_value, exception)
|
|
165
|
-
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
|
166
|
-
template_call.ready = true
|
|
157
|
+
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
|
158
|
+
class RenderHandler < AppMap::Hook::Method
|
|
159
|
+
def handle_call(receiver, args)
|
|
160
|
+
# context, options
|
|
161
|
+
_, options = args
|
|
162
|
+
|
|
163
|
+
warn "Renderer: #{options}" if LOG
|
|
167
164
|
|
|
168
|
-
|
|
165
|
+
TemplateCall.new(receiver).tap do |call|
|
|
166
|
+
Thread.current[TEMPLATE_RENDERER] ||= []
|
|
167
|
+
Thread.current[TEMPLATE_RENDERER] << call
|
|
169
168
|
end
|
|
170
169
|
end
|
|
170
|
+
|
|
171
|
+
def handle_return(call_event_id, elapsed, _return_value, _exception)
|
|
172
|
+
template_call = Array(Thread.current[TEMPLATE_RENDERER]).pop
|
|
173
|
+
template_call.ready = true
|
|
174
|
+
|
|
175
|
+
AppMap::Event::MethodReturnIgnoreValue.build_from_invocation(call_event_id, elapsed: elapsed)
|
|
176
|
+
end
|
|
171
177
|
end
|
|
172
178
|
end
|
|
173
179
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
def ruby2_keywords(*); end unless respond_to?(:ruby2_keywords, true)
|
|
4
|
+
|
|
5
|
+
module AppMap
|
|
6
|
+
class Hook
|
|
7
|
+
# Delegation methods for Ruby 2.
|
|
8
|
+
# cf. https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
|
|
9
|
+
class Method
|
|
10
|
+
ruby2_keywords def call(receiver, *args, &block)
|
|
11
|
+
return do_call(receiver, *args, &block) unless trace?
|
|
12
|
+
|
|
13
|
+
call_event = with_disabled_hook { before_hook receiver, *args }
|
|
14
|
+
trace_call call_event, receiver, *args, &block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def before_hook(receiver, *args)
|
|
20
|
+
call_event = handle_call(receiver, args)
|
|
21
|
+
if call_event
|
|
22
|
+
AppMap.tracing.record_event \
|
|
23
|
+
call_event,
|
|
24
|
+
package: hook_package,
|
|
25
|
+
defined_class: defined_class,
|
|
26
|
+
method: hook_method
|
|
27
|
+
end
|
|
28
|
+
call_event
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ruby2_keywords def do_call(receiver, *args, &block)
|
|
32
|
+
hook_method.bind(receiver).call(*args, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
ruby2_keywords def trace_call(call_event, receiver, *args, &block)
|
|
36
|
+
start_time = gettime
|
|
37
|
+
begin
|
|
38
|
+
return_value = do_call(receiver, *args, &block)
|
|
39
|
+
rescue # rubocop:disable Style/RescueStandardError
|
|
40
|
+
exception = $ERROR_INFO
|
|
41
|
+
raise
|
|
42
|
+
ensure
|
|
43
|
+
with_disabled_hook { after_hook receiver, call_event, gettime - start_time, return_value, exception } \
|
|
44
|
+
if call_event
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def hook_method_def
|
|
49
|
+
this = self
|
|
50
|
+
proc { |*args, &block| this.call self, *args, &block }.tap do |hook|
|
|
51
|
+
hook.ruby2_keywords if hook.respond_to? :ruby2_keywords
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|