backtracie 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +4 -0
- data/README.adoc +102 -2
- data/backtracie.gemspec +8 -2
- data/ext/backtracie_native_extension/backtracie.c +264 -78
- data/ext/backtracie_native_extension/extconf.rb +44 -0
- data/ext/backtracie_native_extension/ruby_shards.c +585 -0
- data/ext/backtracie_native_extension/ruby_shards.h +175 -0
- data/lib/backtracie/location.rb +12 -1
- data/lib/backtracie/version.rb +1 -1
- metadata +8 -16
- data/.gitignore +0 -33
- data/.rspec +0 -2
- data/.ruby-version +0 -1
- data/.standard.yml +0 -14
- data/DEVELOPMENT_NOTES.adoc +0 -399
- data/Rakefile +0 -29
- data/bin/console +0 -8
- data/bin/setup +0 -8
- data/ext/backtracie_native_extension/ruby_3.0.0.c +0 -196
- data/ext/backtracie_native_extension/ruby_3.0.0.h +0 -7
- data/gems.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 887db5ff6ff55d3cf758d7ad192d5a01400a6c31ba6e46067073cf4bff0d34ac
|
4
|
+
data.tar.gz: 4b6b131ebfa4071b5dc222ca9758b30b22f8175c373de2813c85c2bd732a8a5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36e6e23f5eeeef37f14ca586891b9bdd06c44b5dc1b0ab984858f298ff87fb94f38eff9702521fb163587dd9771b73d3f208f98ce059d018a055830320fcc00d
|
7
|
+
data.tar.gz: 1b598ed425ae8ee01a51228c15886dc246c4324a8d4371c54fa36b49d6b66e08ffebff26af201cf3d756867aa7f3ae27dac791454984ebddb641ae0c693b021e
|
data/.editorconfig
CHANGED
data/README.adoc
CHANGED
@@ -4,8 +4,13 @@
|
|
4
4
|
:toclevels: 4
|
5
5
|
:toc-title:
|
6
6
|
|
7
|
+
A Ruby gem for beautiful backtraces.
|
8
|
+
|
9
|
+
Aims to offer alternatives to `Thread#backtrace`, `Thread#backtrace_locations`, `Kernel#caller` and `Kernel#caller_locations` with similar (or better!?) performance, but with better metadata (such as class names).
|
10
|
+
|
7
11
|
image:https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg["Contributor Covenant", link="CODE_OF_CONDUCT.adoc"]
|
8
12
|
image:https://badge.fury.io/rb/backtracie.svg["Gem Version", link="https://badge.fury.io/rb/backtracie"]
|
13
|
+
image:https://github.com/ivoanjo/backtracie/actions/workflows/test.yml/badge.svg["CI Status", link="https://github.com/ivoanjo/backtracie/actions/workflows/test.yml"]
|
9
14
|
|
10
15
|
[discrete]
|
11
16
|
== Contents
|
@@ -39,7 +44,75 @@ This gem is versioned according to http://semver.org/spec/v2.0.0.html[Semantic V
|
|
39
44
|
|
40
45
|
== Usage
|
41
46
|
|
42
|
-
|
47
|
+
Currently, `backtracie` exposes two APIs:
|
48
|
+
|
49
|
+
* `Backtracie.backtrace_locations(thread)`: Returns an array representing the backtrace of the given `thread`. Similar to `Thread#backtrace_locations`.
|
50
|
+
* `Backtracie.caller_locations`: Returns an array representing the backtrace of the current thread, starting from the caller of the current method. the Similar to `Kernel#caller_locations`.
|
51
|
+
|
52
|
+
These methods, inspired by their original Ruby counterparts, return arrays of `Backtracie::Location` items. These items return A LOT more information than Ruby usually exposes:
|
53
|
+
|
54
|
+
[source,ruby]
|
55
|
+
----
|
56
|
+
$ bundle exec pry
|
57
|
+
[1] pry(main)> require 'backtracie'
|
58
|
+
|
59
|
+
[2] pry(main)> class Example
|
60
|
+
[2] pry(main)* def foo
|
61
|
+
[2] pry(main)* bar
|
62
|
+
[2] pry(main)* end
|
63
|
+
[2] pry(main)* def bar
|
64
|
+
[2] pry(main)* Backtracie.caller_locations.first
|
65
|
+
[2] pry(main)* end
|
66
|
+
[2] pry(main)* end
|
67
|
+
|
68
|
+
[3] pry(main)> Example.new.foo
|
69
|
+
=> #<Backtracie::Location:0x0000561b236c9208
|
70
|
+
@absolute_path="(pry)",
|
71
|
+
@base_label="foo",
|
72
|
+
@debug=
|
73
|
+
{:ruby_frame?=>true,
|
74
|
+
:should_use_iseq=>false,
|
75
|
+
:should_use_cfunc_name=>false,
|
76
|
+
:vm_method_type=>0,
|
77
|
+
:line_number=>3,
|
78
|
+
:called_id=>:foo,
|
79
|
+
:defined_class_refinement?=>false,
|
80
|
+
:self_class=>Example,
|
81
|
+
:real_class=>Example,
|
82
|
+
:self=>#<Example:0x0000561b236f3a08>,
|
83
|
+
:self_class_singleton?=>false,
|
84
|
+
:iseq_is_block?=>false,
|
85
|
+
:iseq_is_eval?=>false,
|
86
|
+
:cfunc_name=>nil,
|
87
|
+
:iseq=>
|
88
|
+
{:path=>"(pry)",
|
89
|
+
:absolute_path=>"(pry)",
|
90
|
+
:label=>"foo",
|
91
|
+
:base_label=>"foo",
|
92
|
+
:full_label=>"foo",
|
93
|
+
:first_lineno=>2,
|
94
|
+
:classpath=>nil,
|
95
|
+
:singleton_method_p=>false,
|
96
|
+
:method_name=>"foo",
|
97
|
+
:qualified_method_name=>"foo"},
|
98
|
+
:callable_method_entry=>
|
99
|
+
{:path=>"(pry)",
|
100
|
+
:absolute_path=>"(pry)",
|
101
|
+
:label=>"foo",
|
102
|
+
:base_label=>"foo",
|
103
|
+
:full_label=>"Example#foo",
|
104
|
+
:first_lineno=>2,
|
105
|
+
:classpath=>"Example",
|
106
|
+
:singleton_method_p=>false,
|
107
|
+
:method_name=>"foo",
|
108
|
+
:qualified_method_name=>"Example#foo"}},
|
109
|
+
@label="foo",
|
110
|
+
@lineno=3,
|
111
|
+
@path="(pry)",
|
112
|
+
@qualified_method_name="Example#foo">
|
113
|
+
----
|
114
|
+
|
115
|
+
This information can be used to to create much richer stack traces than the ones exposed by Ruby, including details such as class and module names, if methods are singletons, etc.
|
43
116
|
|
44
117
|
== Development
|
45
118
|
|
@@ -49,13 +122,16 @@ To open a console with the gem loaded, run `bundle console`.
|
|
49
122
|
|
50
123
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].
|
51
124
|
|
125
|
+
To test on specific Ruby versions you can use docker. E.g. to test on Ruby 2.6, use `docker-compose run ruby-2.6`.
|
126
|
+
To test on all rubies using docker, you can use `bundle exec rake test-all`.
|
127
|
+
|
52
128
|
== Feedback and success stories
|
53
129
|
|
54
130
|
Your feedback is welcome!
|
55
131
|
|
56
132
|
== Contributing
|
57
133
|
|
58
|
-
Bug reports and pull requests are welcome
|
134
|
+
Bug reports and pull requests are welcome at https://github.com/ivoanjo/backtracie.
|
59
135
|
|
60
136
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the http://contributor-covenant.org[Contributor Covenant] code of conduct.
|
61
137
|
|
@@ -64,3 +140,27 @@ Maintained with ❤️ by https://ivoanjo.me/[Ivo Anjo].
|
|
64
140
|
== Code of Conduct
|
65
141
|
|
66
142
|
Everyone interacting in the backtracie project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the link:CODE_OF_CONDUCT.adoc[code of conduct].
|
143
|
+
|
144
|
+
== Interesting Links
|
145
|
+
|
146
|
+
Here's some gems that are doing similar things to `backtracie`:
|
147
|
+
|
148
|
+
* https://github.com/tmm1/stackprof: A sampling call-stack profiler for Ruby
|
149
|
+
* https://github.com/ko1/pretty_backtrace: Pretty your exception backtrace
|
150
|
+
* https://github.com/Shopify/stack_frames: This library allows backtraces to be captured and accessed without object allocations by leveraging MRI's profile frames API
|
151
|
+
|
152
|
+
Other interesting links on this matter:
|
153
|
+
|
154
|
+
* https://github.com/ruby/ruby/pull/3299: vm_backtrace.c: let rb_profile_frames show cfunc frames
|
155
|
+
* https://github.com/ruby/ruby/pull/2713: Fix use of the rb_profile_frames start parameter
|
156
|
+
* https://github.com/rake-compiler/rake-compiler: Provide a standard and simplified way to build and package Ruby C and Java extensions using Rake as glue.
|
157
|
+
* https://github.com/ko1/rubyhackchallenge: "Ruby Hack Challenge" (RHC) is a short guide to hack MRI (Matz Ruby Interpreter) internals
|
158
|
+
* https://docs.ruby-lang.org/en/3.0.0/doc/extension_rdoc.html: Creating Extension Libraries for Ruby
|
159
|
+
* https://ruby-hacking-guide.github.io/: Ruby Hacking Guide
|
160
|
+
** This is one of the most through and deep guides out there to the MRI internals. Very detailed and in depth, but outdated.
|
161
|
+
* http://patshaughnessy.net/ruby-under-a-microscope: Book with a really good introduction to MRI internals.
|
162
|
+
|
163
|
+
My blog posts on better backtraces:
|
164
|
+
|
165
|
+
* https://ivoanjo.me/blog/2020/07/05/ruby-experiment-include-class-names-in-backtraces/: ruby experiment: include class names in backtraces
|
166
|
+
* https://ivoanjo.me/blog/2020/07/19/better-backtraces-in-ruby-using-tracepoint/: better backtraces in ruby using tracepoint
|
data/backtracie.gemspec
CHANGED
@@ -32,12 +32,18 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.description = "Ruby gem for beautiful backtraces"
|
33
33
|
spec.homepage = "https://github.com/ivoanjo/backtracie"
|
34
34
|
spec.license = "LGPL-3.0+"
|
35
|
-
spec.required_ruby_version = ">= 3.0
|
35
|
+
spec.required_ruby_version = ">= 2.3.0"
|
36
36
|
|
37
37
|
# Specify which files should be added to the gem when it is released.
|
38
38
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
39
39
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
40
|
-
`git ls-files -z`.split("\x0")
|
40
|
+
`git ls-files -z`.split("\x0")
|
41
|
+
.reject { |f| f.match(%r{\A(?:test|spec|features|[.]github)/}) }
|
42
|
+
.reject { |f|
|
43
|
+
["gems.rb", ".whitesource", ".ruby-version", ".gitignore", ".rspec", ".standard.yml",
|
44
|
+
"DEVELOPMENT_NOTES.adoc", "Rakefile", "docker-compose.yml", "bin/console"].include?(f)
|
45
|
+
}
|
41
46
|
end
|
42
47
|
spec.require_paths = ["lib", "ext"]
|
48
|
+
spec.extensions = ["ext/backtracie_native_extension/extconf.rb"]
|
43
49
|
end
|
@@ -16,44 +16,56 @@
|
|
16
16
|
// You should have received a copy of the GNU Lesser General Public License
|
17
17
|
// along with backtracie. If not, see <http://www.gnu.org/licenses/>.
|
18
18
|
|
19
|
+
#include <stdbool.h>
|
20
|
+
|
19
21
|
#include "ruby/ruby.h"
|
20
22
|
#include "ruby/debug.h"
|
21
23
|
|
22
24
|
#include "extconf.h"
|
23
25
|
|
24
|
-
#include "
|
25
|
-
|
26
|
-
// Constants
|
26
|
+
#include "ruby_shards.h"
|
27
27
|
|
28
28
|
#define MAX_STACK_DEPTH 2000 // FIXME: Need to handle when this is not enough
|
29
29
|
|
30
|
-
|
30
|
+
#define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
|
31
|
+
#define SAFE_NAVIGATION(function, maybe_nil) ((maybe_nil) != Qnil ? function(maybe_nil) : Qnil)
|
31
32
|
|
33
|
+
static VALUE main_object_instance = Qnil;
|
34
|
+
static ID ensure_object_is_thread_id;
|
35
|
+
static ID to_s_id;
|
36
|
+
static ID current_id;
|
37
|
+
static ID backtrace_locations_id;
|
32
38
|
static VALUE backtracie_module = Qnil;
|
33
39
|
static VALUE backtracie_location_class = Qnil;
|
34
40
|
|
35
|
-
// Function headers
|
36
|
-
|
37
41
|
static VALUE primitive_caller_locations(VALUE self);
|
38
42
|
static VALUE primitive_backtrace_locations(VALUE self, VALUE thread);
|
39
|
-
static VALUE
|
40
|
-
inline static VALUE new_location(VALUE absolute_path, VALUE base_label, VALUE label, VALUE lineno, VALUE path, VALUE debug);
|
41
|
-
static
|
42
|
-
static VALUE
|
43
|
-
static VALUE cfunc_frame_to_location(
|
44
|
-
static VALUE
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames);
|
44
|
+
inline static VALUE new_location(VALUE absolute_path, VALUE base_label, VALUE label, VALUE lineno, VALUE path, VALUE qualified_method_name, VALUE debug);
|
45
|
+
static VALUE ruby_frame_to_location(raw_location *the_location);
|
46
|
+
static VALUE qualified_method_name_for_location(raw_location *the_location);
|
47
|
+
static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location);
|
48
|
+
static VALUE frame_from_location(raw_location *the_location);
|
49
|
+
static VALUE qualified_method_name_for_block(raw_location *the_location);
|
50
|
+
static VALUE qualified_method_name_from_self(raw_location *the_location);
|
51
|
+
static bool is_self_class_singleton(raw_location *the_location);
|
52
|
+
static bool is_defined_class_a_refinement(raw_location *the_location);
|
53
|
+
static VALUE debug_raw_location(raw_location *the_location);
|
54
|
+
static VALUE debug_frame(VALUE frame);
|
55
|
+
static inline VALUE to_boolean(bool value) ;
|
49
56
|
|
50
57
|
void Init_backtracie_native_extension(void) {
|
58
|
+
main_object_instance = backtracie_rb_vm_top_self();
|
59
|
+
ensure_object_is_thread_id = rb_intern("ensure_object_is_thread");
|
60
|
+
to_s_id = rb_intern("to_s");
|
61
|
+
current_id = rb_intern("current");
|
62
|
+
backtrace_locations_id = rb_intern("backtrace_locations");
|
63
|
+
|
51
64
|
backtracie_module = rb_const_get(rb_cObject, rb_intern("Backtracie"));
|
52
65
|
rb_global_variable(&backtracie_module);
|
53
66
|
|
54
67
|
rb_define_module_function(backtracie_module, "backtrace_locations", primitive_backtrace_locations, 1);
|
55
68
|
|
56
|
-
// We need to keep a reference to Backtracie::Locations around, to create new instances
|
57
69
|
backtracie_location_class = rb_const_get(backtracie_module, rb_intern("Location"));
|
58
70
|
rb_global_variable(&backtracie_location_class);
|
59
71
|
|
@@ -63,24 +75,30 @@ void Init_backtracie_native_extension(void) {
|
|
63
75
|
}
|
64
76
|
|
65
77
|
// Get array of Backtracie::Locations for a given thread; if thread is nil, returns for the current thread
|
66
|
-
static VALUE
|
78
|
+
static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames) {
|
67
79
|
int stack_depth = 0;
|
68
|
-
|
69
|
-
VALUE correct_labels[MAX_STACK_DEPTH];
|
70
|
-
int lines[MAX_STACK_DEPTH];
|
80
|
+
raw_location raw_locations[MAX_STACK_DEPTH];
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
#ifndef PRE_MJIT_RUBY
|
83
|
+
if (thread == Qnil) {
|
84
|
+
// Get for own thread
|
85
|
+
stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
|
86
|
+
} else {
|
87
|
+
stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
|
88
|
+
}
|
89
|
+
#else
|
90
|
+
VALUE current_thread = rb_funcall(rb_cThread, current_id, 0);
|
91
|
+
|
92
|
+
if (thread == Qnil || thread == current_thread) {
|
93
|
+
thread = current_thread;
|
94
|
+
// Since we're going to sample the current thread, we don't want Thread#backtrace_locations to show up
|
95
|
+
// on the stack (as we want to match the exact depth we get from MRI), so we ignore one extra frame
|
96
|
+
ignored_stack_top_frames++;
|
97
|
+
}
|
82
98
|
|
83
|
-
|
99
|
+
VALUE locations_array = rb_funcall(thread, backtrace_locations_id, 0);
|
100
|
+
stack_depth = backtracie_profile_frames_from_ruby_locations(locations_array, raw_locations);
|
101
|
+
#endif
|
84
102
|
|
85
103
|
VALUE locations = rb_ary_new_capa(stack_depth - ignored_stack_top_frames);
|
86
104
|
|
@@ -88,22 +106,16 @@ static VALUE caller_locations(VALUE self, VALUE thread, int ignored_stack_top_fr
|
|
88
106
|
// Kernel#caller_locations is to instead use the path and line number of the last Ruby frame seen.
|
89
107
|
// Thus, we keep that frame here to able to replicate that behavior.
|
90
108
|
// (This is why we also iterate the frames array backwards below -- so that it's easier to keep the last_ruby_frame)
|
91
|
-
|
92
|
-
VALUE last_ruby_line = Qnil;
|
109
|
+
raw_location *last_ruby_location = 0;
|
93
110
|
|
94
111
|
for (int i = stack_depth - 1; i >= ignored_stack_top_frames; i--) {
|
95
|
-
VALUE frame = frames[i];
|
96
|
-
int line = lines[i];
|
97
|
-
|
98
112
|
VALUE location = Qnil;
|
99
113
|
|
100
|
-
if (is_ruby_frame
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
location = ruby_frame_to_location(frame, last_ruby_line, correct_labels[i]);
|
114
|
+
if (raw_locations[i].is_ruby_frame) {
|
115
|
+
last_ruby_location = &raw_locations[i];
|
116
|
+
location = ruby_frame_to_location(&raw_locations[i]);
|
105
117
|
} else {
|
106
|
-
location = cfunc_frame_to_location(
|
118
|
+
location = cfunc_frame_to_location(&raw_locations[i], last_ruby_location);
|
107
119
|
}
|
108
120
|
|
109
121
|
rb_ary_store(locations, i - ignored_stack_top_frames, location);
|
@@ -119,67 +131,241 @@ static VALUE primitive_caller_locations(VALUE self) {
|
|
119
131
|
// * the frame from the caller itself (since we're replicating the semantics of Kernel#caller_locations)
|
120
132
|
int ignored_stack_top_frames = 3;
|
121
133
|
|
122
|
-
return
|
134
|
+
return collect_backtrace_locations(self, Qnil, ignored_stack_top_frames);
|
123
135
|
}
|
124
136
|
|
125
137
|
static VALUE primitive_backtrace_locations(VALUE self, VALUE thread) {
|
126
|
-
rb_funcall(backtracie_module,
|
138
|
+
rb_funcall(backtracie_module, ensure_object_is_thread_id, 1, thread);
|
127
139
|
|
128
140
|
int ignored_stack_top_frames = 0;
|
129
141
|
|
130
|
-
return
|
142
|
+
return collect_backtrace_locations(self, thread, ignored_stack_top_frames);
|
131
143
|
}
|
132
144
|
|
133
|
-
inline static VALUE new_location(
|
134
|
-
VALUE
|
145
|
+
inline static VALUE new_location(
|
146
|
+
VALUE absolute_path,
|
147
|
+
VALUE base_label,
|
148
|
+
VALUE label,
|
149
|
+
VALUE lineno,
|
150
|
+
VALUE path,
|
151
|
+
VALUE qualified_method_name,
|
152
|
+
VALUE debug
|
153
|
+
) {
|
154
|
+
VALUE arguments[] = { absolute_path, base_label, label, lineno, path, qualified_method_name, debug };
|
135
155
|
return rb_class_new_instance(VALUE_COUNT(arguments), arguments, backtracie_location_class);
|
136
156
|
}
|
137
157
|
|
138
|
-
static
|
139
|
-
VALUE
|
158
|
+
static VALUE ruby_frame_to_location(raw_location *the_location) {
|
159
|
+
VALUE frame = frame_from_location(the_location);
|
140
160
|
|
141
|
-
return (rb_profile_frame_path(frame) != Qnil || absolute_path != Qnil) &&
|
142
|
-
(rb_funcall(absolute_path, rb_intern("=="), 1, rb_str_new2("<cfunc>")) == Qfalse);
|
143
|
-
}
|
144
|
-
|
145
|
-
static VALUE ruby_frame_to_location(VALUE frame, VALUE last_ruby_line, VALUE correct_label) {
|
146
161
|
return new_location(
|
147
162
|
rb_profile_frame_absolute_path(frame),
|
148
163
|
rb_profile_frame_base_label(frame),
|
149
|
-
rb_profile_frame_label(
|
150
|
-
|
164
|
+
rb_profile_frame_label(the_location->iseq),
|
165
|
+
INT2FIX(the_location->line_number),
|
151
166
|
rb_profile_frame_path(frame),
|
152
|
-
|
167
|
+
qualified_method_name_for_location(the_location),
|
168
|
+
debug_raw_location(the_location)
|
153
169
|
);
|
154
170
|
}
|
155
171
|
|
156
|
-
static VALUE
|
157
|
-
VALUE
|
172
|
+
static VALUE qualified_method_name_for_location(raw_location *the_location) {
|
173
|
+
VALUE frame = frame_from_location(the_location);
|
174
|
+
VALUE defined_class = backtracie_defined_class(the_location);
|
175
|
+
VALUE qualified_method_name = Qnil;
|
176
|
+
|
177
|
+
if (is_defined_class_a_refinement(the_location)) {
|
178
|
+
qualified_method_name = backtracie_refinement_name(the_location);
|
179
|
+
rb_str_concat(qualified_method_name, rb_str_new2("#"));
|
180
|
+
rb_str_concat(qualified_method_name, rb_profile_frame_label(frame));
|
181
|
+
} else if (is_self_class_singleton(the_location)) {
|
182
|
+
qualified_method_name = qualified_method_name_from_self(the_location);
|
183
|
+
} else if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
184
|
+
qualified_method_name = qualified_method_name_for_block(the_location);
|
185
|
+
} else if (defined_class != Qnil && rb_mod_name(defined_class) == Qnil) {
|
186
|
+
// Instance of an anonymous class. Let's find it a name
|
187
|
+
VALUE superclass = defined_class;
|
188
|
+
VALUE superclass_name = Qnil;
|
189
|
+
do {
|
190
|
+
superclass = RCLASS_SUPER(superclass);
|
191
|
+
superclass_name = rb_mod_name(superclass);
|
192
|
+
} while (superclass_name == Qnil);
|
193
|
+
|
194
|
+
qualified_method_name = rb_str_new2("");
|
195
|
+
rb_str_concat(qualified_method_name, superclass_name);
|
196
|
+
rb_str_concat(qualified_method_name, rb_str_new2("$anonymous#"));
|
197
|
+
rb_str_concat(qualified_method_name, rb_profile_frame_base_label(frame_from_location(the_location)));
|
198
|
+
} else {
|
199
|
+
qualified_method_name = backtracie_rb_profile_frame_qualified_method_name(frame);
|
200
|
+
|
201
|
+
if (qualified_method_name == Qnil) {
|
202
|
+
qualified_method_name = qualified_method_name_from_self(the_location);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
207
|
+
rb_str_concat(qualified_method_name, rb_str_new2("{block}"));
|
208
|
+
}
|
209
|
+
|
210
|
+
return qualified_method_name;
|
211
|
+
}
|
212
|
+
|
213
|
+
static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location) {
|
214
|
+
VALUE last_ruby_frame =
|
215
|
+
last_ruby_location != 0 ? frame_from_location(last_ruby_location) : Qnil;
|
216
|
+
|
217
|
+
// Replaces label and base_label in cfuncs
|
218
|
+
VALUE method_name =
|
219
|
+
the_location->should_use_cfunc_name ?
|
220
|
+
the_location->cfunc_name : backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
|
158
221
|
|
159
222
|
return new_location(
|
160
|
-
|
223
|
+
SAFE_NAVIGATION(rb_profile_frame_absolute_path, last_ruby_frame),
|
161
224
|
method_name,
|
162
225
|
method_name,
|
163
|
-
|
164
|
-
|
165
|
-
|
226
|
+
last_ruby_location != 0 ? INT2FIX(last_ruby_location->line_number) : Qnil,
|
227
|
+
SAFE_NAVIGATION(rb_profile_frame_path, last_ruby_frame),
|
228
|
+
backtracie_rb_profile_frame_qualified_method_name(the_location->callable_method_entry),
|
229
|
+
debug_raw_location(the_location)
|
166
230
|
);
|
167
231
|
}
|
168
232
|
|
169
|
-
|
170
|
-
|
233
|
+
static VALUE frame_from_location(raw_location *the_location) {
|
234
|
+
return \
|
235
|
+
the_location->should_use_iseq ||
|
236
|
+
// This one is somewhat weird, but the regular MRI Ruby APIs seem to pick the iseq for evals as well
|
237
|
+
backtracie_iseq_is_eval(the_location) ?
|
238
|
+
the_location->iseq : the_location->callable_method_entry;
|
239
|
+
}
|
240
|
+
|
241
|
+
static VALUE qualified_method_name_for_block(raw_location *the_location) {
|
242
|
+
VALUE class_name = backtracie_rb_profile_frame_classpath(the_location->callable_method_entry);
|
243
|
+
VALUE method_name = backtracie_called_id(the_location);
|
244
|
+
VALUE is_singleton_method = rb_profile_frame_singleton_method_p(the_location->iseq);
|
245
|
+
|
246
|
+
VALUE name = rb_str_new2("");
|
247
|
+
rb_str_concat(name, class_name);
|
248
|
+
rb_str_concat(name, is_singleton_method ? rb_str_new2(".") : rb_str_new2("#"));
|
249
|
+
rb_str_concat(name, rb_sym2str(method_name));
|
250
|
+
|
251
|
+
return name;
|
252
|
+
}
|
253
|
+
|
254
|
+
static VALUE qualified_method_name_from_self(raw_location *the_location) {
|
255
|
+
if (the_location->self == Qnil) return Qnil;
|
256
|
+
|
257
|
+
VALUE self_class = rb_class_of(the_location->self);
|
258
|
+
bool is_self_class_singleton = FL_TEST(self_class, FL_SINGLETON);
|
259
|
+
|
260
|
+
VALUE name = rb_str_new2("");
|
261
|
+
if (is_self_class_singleton) {
|
262
|
+
if (the_location->self == main_object_instance) {
|
263
|
+
rb_str_concat(name, rb_str_new2("Object$<main>#"));
|
264
|
+
} else {
|
265
|
+
// Crawl up the hierarchy to find a real class
|
266
|
+
VALUE the_class = rb_class_real(self_class);
|
267
|
+
|
268
|
+
// This is similar to BetterBacktraceHelper's self_class_module_or_class?
|
269
|
+
// If the real class of this object is the actual class Class or the class Module,
|
270
|
+
// it means that this is a method being directly called on a given Class/Module,
|
271
|
+
// e.g. Kernel.puts or BasicObject.name. In this case, Ruby already sets the name
|
272
|
+
// correctly, so we just delegate.
|
273
|
+
if (the_class == rb_cModule || the_class == rb_cClass) {
|
274
|
+
// Is the class/module is anonymous?
|
275
|
+
if (rb_mod_name(the_location->self) == Qnil) {
|
276
|
+
rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
|
277
|
+
rb_str_concat(name, rb_str_new2("$singleton."));
|
278
|
+
} else {
|
279
|
+
return SAFE_NAVIGATION(backtracie_rb_profile_frame_qualified_method_name, the_location->callable_method_entry);
|
280
|
+
}
|
281
|
+
} else {
|
282
|
+
rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
|
283
|
+
rb_str_concat(name, rb_str_new2("$singleton#"));
|
284
|
+
}
|
285
|
+
}
|
286
|
+
} else {
|
287
|
+
// Not very sure if this branch of the if is ever reached, and if it would be for a instance or static call, so
|
288
|
+
// let's just have these defaults and revisit as needed
|
289
|
+
rb_str_concat(name, rb_funcall(self_class, to_s_id, 0));
|
290
|
+
rb_str_concat(name, rb_str_new2("#"));
|
291
|
+
}
|
292
|
+
|
293
|
+
if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
294
|
+
// Nothing to do, {block} will be appended in qualified_method_name_for_location which called us
|
295
|
+
} else {
|
296
|
+
rb_str_concat(name, rb_profile_frame_base_label(frame_from_location(the_location)));
|
297
|
+
}
|
298
|
+
|
299
|
+
return name;
|
300
|
+
}
|
301
|
+
|
302
|
+
static bool is_self_class_singleton(raw_location *the_location) {
|
303
|
+
return the_location->self != Qnil && FL_TEST(rb_class_of(the_location->self), FL_SINGLETON);
|
304
|
+
}
|
305
|
+
|
306
|
+
static bool is_defined_class_a_refinement(raw_location *the_location) {
|
307
|
+
VALUE defined_class = backtracie_defined_class(the_location);
|
308
|
+
|
309
|
+
return defined_class != Qnil && FL_TEST(rb_class_of(defined_class), RMODULE_IS_REFINEMENT);
|
310
|
+
}
|
311
|
+
|
312
|
+
static VALUE debug_raw_location(raw_location *the_location) {
|
313
|
+
VALUE self_class = SAFE_NAVIGATION(rb_class_of, the_location->self);
|
314
|
+
|
171
315
|
VALUE arguments[] = {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
316
|
+
ID2SYM(rb_intern("ruby_frame?")) , /* => */ to_boolean(the_location->is_ruby_frame),
|
317
|
+
ID2SYM(rb_intern("should_use_iseq")), /* => */ to_boolean(the_location->should_use_iseq),
|
318
|
+
ID2SYM(rb_intern("should_use_cfunc_name")), /* => */ to_boolean(the_location->should_use_cfunc_name),
|
319
|
+
ID2SYM(rb_intern("vm_method_type")), /* => */ INT2FIX(the_location->vm_method_type),
|
320
|
+
ID2SYM(rb_intern("line_number")), /* => */ INT2FIX(the_location->line_number),
|
321
|
+
ID2SYM(rb_intern("called_id")), /* => */ backtracie_called_id(the_location),
|
322
|
+
// TODO: On Ruby < 3.0, running be pry -e 'require "backtracie"; Backtracie.caller_locations' with this being
|
323
|
+
// exposed causes a VM segfault inside the pretty printing code
|
324
|
+
// ID2SYM(rb_intern("defined_class")), /* => */ backtracie_defined_class(the_location),
|
325
|
+
ID2SYM(rb_intern("defined_class_refinement?")), /* => */ to_boolean(is_defined_class_a_refinement(the_location)),
|
326
|
+
ID2SYM(rb_intern("self_class")), /* => */ self_class,
|
327
|
+
ID2SYM(rb_intern("real_class")), /* => */ SAFE_NAVIGATION(rb_class_real, self_class),
|
328
|
+
ID2SYM(rb_intern("self")), /* => */ the_location->self,
|
329
|
+
ID2SYM(rb_intern("self_class_singleton?")), /* => */ to_boolean(is_self_class_singleton(the_location)),
|
330
|
+
ID2SYM(rb_intern("iseq_is_block?")), /* => */ to_boolean(backtracie_iseq_is_block(the_location)),
|
331
|
+
ID2SYM(rb_intern("iseq_is_eval?")), /* => */ to_boolean(backtracie_iseq_is_eval(the_location)),
|
332
|
+
ID2SYM(rb_intern("cfunc_name")), /* => */ the_location->cfunc_name,
|
333
|
+
ID2SYM(rb_intern("iseq")), /* => */ debug_frame(the_location->iseq),
|
334
|
+
ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry)
|
335
|
+
};
|
336
|
+
|
337
|
+
VALUE debug_hash = rb_hash_new();
|
338
|
+
#ifndef PRE_MJIT_RUBY
|
339
|
+
// FIXME: Hack, need to actually fix this
|
340
|
+
rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
|
341
|
+
#endif
|
342
|
+
return debug_hash;
|
343
|
+
}
|
344
|
+
|
345
|
+
static VALUE debug_frame(VALUE frame) {
|
346
|
+
if (frame == Qnil) return Qnil;
|
347
|
+
|
348
|
+
VALUE arguments[] = {
|
349
|
+
ID2SYM(rb_intern("path")), /* => */ rb_profile_frame_path(frame),
|
350
|
+
ID2SYM(rb_intern("absolute_path")), /* => */ rb_profile_frame_absolute_path(frame),
|
351
|
+
ID2SYM(rb_intern("label")), /* => */ rb_profile_frame_label(frame),
|
352
|
+
ID2SYM(rb_intern("base_label")), /* => */ rb_profile_frame_base_label(frame),
|
353
|
+
ID2SYM(rb_intern("full_label")), /* => */ rb_profile_frame_full_label(frame),
|
354
|
+
ID2SYM(rb_intern("first_lineno")), /* => */ rb_profile_frame_first_lineno(frame),
|
355
|
+
ID2SYM(rb_intern("classpath")), /* => */ backtracie_rb_profile_frame_classpath(frame),
|
356
|
+
ID2SYM(rb_intern("singleton_method_p")), /* => */ rb_profile_frame_singleton_method_p(frame),
|
357
|
+
ID2SYM(rb_intern("method_name")), /* => */ rb_profile_frame_method_name(frame),
|
358
|
+
ID2SYM(rb_intern("qualified_method_name")), /* => */ backtracie_rb_profile_frame_qualified_method_name(frame)
|
183
359
|
};
|
184
|
-
|
360
|
+
|
361
|
+
VALUE debug_hash = rb_hash_new();
|
362
|
+
#ifndef PRE_MJIT_RUBY
|
363
|
+
// FIXME: Hack, need to actually fix this
|
364
|
+
rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
|
365
|
+
#endif
|
366
|
+
return debug_hash;
|
367
|
+
}
|
368
|
+
|
369
|
+
static inline VALUE to_boolean(bool value) {
|
370
|
+
return value ? Qtrue : Qfalse;
|
185
371
|
}
|