backtracie 0.1.0 → 0.3.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/.editorconfig +4 -0
- data/README.adoc +107 -2
- data/backtracie.gemspec +11 -2
- data/ext/backtracie_native_extension/backtracie.c +235 -75
- data/ext/backtracie_native_extension/extconf.rb +76 -1
- data/ext/backtracie_native_extension/ruby_shards.c +533 -0
- data/ext/backtracie_native_extension/{ruby_3.0.0.c → ruby_shards.h} +81 -107
- data/lib/backtracie/location.rb +12 -1
- data/lib/backtracie/version.rb +1 -1
- data/lib/backtracie.rb +11 -2
- metadata +29 -17
- 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.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: b8ef71cf1cfd9ca005e0ecb0fe7434a9c8121bc46fc70c34209c74ecad1c705c
|
4
|
+
data.tar.gz: 723d3c66fb919432481428609a8316243e160805ff4142757f9ec65cf254f2d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceffbd048f44b6563298f301bde8dd498c1ea92be244e90a42390f4d387d58ef3837d936afbd4c63376ebfa2a20984989a2f8e1426ab9cccc30fc8c844c78a68
|
7
|
+
data.tar.gz: cc6e34b726934f01ae92bf089954bace7063619ce8e3d71b1d1eea610d153f10641257399388ef70bcd7730b1aea89039d4f7e6e2b960f2b09e937fd4d802969
|
data/.editorconfig
CHANGED
data/README.adoc
CHANGED
@@ -4,8 +4,18 @@
|
|
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
|
+
|
11
|
+
WARNING: **⚠️ This gem is currently an exploration ground for the use of low-level techniques to get at internal Ruby VM structures.
|
12
|
+
Due to the way it works, bugs can easily cause your whole Ruby VM to crash;
|
13
|
+
Please take this into account if you plan on using this gem.
|
14
|
+
Still, contributions and bug reports are mega welcome, and I hope to one day be able to remove this warning ⚠️.**
|
15
|
+
|
7
16
|
image:https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg["Contributor Covenant", link="CODE_OF_CONDUCT.adoc"]
|
8
17
|
image:https://badge.fury.io/rb/backtracie.svg["Gem Version", link="https://badge.fury.io/rb/backtracie"]
|
18
|
+
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
19
|
|
10
20
|
[discrete]
|
11
21
|
== Contents
|
@@ -39,7 +49,75 @@ This gem is versioned according to http://semver.org/spec/v2.0.0.html[Semantic V
|
|
39
49
|
|
40
50
|
== Usage
|
41
51
|
|
42
|
-
|
52
|
+
Currently, `backtracie` exposes two APIs:
|
53
|
+
|
54
|
+
* `Backtracie.backtrace_locations(thread)`: Returns an array representing the backtrace of the given `thread`. Similar to `Thread#backtrace_locations`.
|
55
|
+
* `Backtracie.caller_locations`: Returns an array representing the backtrace of the current thread, starting from the caller of the current method. Similar to `Kernel#caller_locations`.
|
56
|
+
|
57
|
+
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:
|
58
|
+
|
59
|
+
[source,ruby]
|
60
|
+
----
|
61
|
+
$ bundle exec pry
|
62
|
+
[1] pry(main)> require 'backtracie'
|
63
|
+
|
64
|
+
[2] pry(main)> class Example
|
65
|
+
[2] pry(main)* def foo
|
66
|
+
[2] pry(main)* bar
|
67
|
+
[2] pry(main)* end
|
68
|
+
[2] pry(main)* def bar
|
69
|
+
[2] pry(main)* Backtracie.caller_locations.first
|
70
|
+
[2] pry(main)* end
|
71
|
+
[2] pry(main)* end
|
72
|
+
|
73
|
+
[3] pry(main)> Example.new.foo
|
74
|
+
=> #<Backtracie::Location:0x0000561b236c9208
|
75
|
+
@absolute_path="(pry)",
|
76
|
+
@base_label="foo",
|
77
|
+
@debug=
|
78
|
+
{:ruby_frame?=>true,
|
79
|
+
:should_use_iseq=>false,
|
80
|
+
:should_use_cfunc_name=>false,
|
81
|
+
:vm_method_type=>0,
|
82
|
+
:line_number=>3,
|
83
|
+
:called_id=>:foo,
|
84
|
+
:defined_class_refinement?=>false,
|
85
|
+
:self_class=>Example,
|
86
|
+
:real_class=>Example,
|
87
|
+
:self=>#<Example:0x0000561b236f3a08>,
|
88
|
+
:self_class_singleton?=>false,
|
89
|
+
:iseq_is_block?=>false,
|
90
|
+
:iseq_is_eval?=>false,
|
91
|
+
:cfunc_name=>nil,
|
92
|
+
:iseq=>
|
93
|
+
{:path=>"(pry)",
|
94
|
+
:absolute_path=>"(pry)",
|
95
|
+
:label=>"foo",
|
96
|
+
:base_label=>"foo",
|
97
|
+
:full_label=>"foo",
|
98
|
+
:first_lineno=>2,
|
99
|
+
:classpath=>nil,
|
100
|
+
:singleton_method_p=>false,
|
101
|
+
:method_name=>"foo",
|
102
|
+
:qualified_method_name=>"foo"},
|
103
|
+
:callable_method_entry=>
|
104
|
+
{:path=>"(pry)",
|
105
|
+
:absolute_path=>"(pry)",
|
106
|
+
:label=>"foo",
|
107
|
+
:base_label=>"foo",
|
108
|
+
:full_label=>"Example#foo",
|
109
|
+
:first_lineno=>2,
|
110
|
+
:classpath=>"Example",
|
111
|
+
:singleton_method_p=>false,
|
112
|
+
:method_name=>"foo",
|
113
|
+
:qualified_method_name=>"Example#foo"}},
|
114
|
+
@label="foo",
|
115
|
+
@lineno=3,
|
116
|
+
@path="(pry)",
|
117
|
+
@qualified_method_name="Example#foo">
|
118
|
+
----
|
119
|
+
|
120
|
+
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
121
|
|
44
122
|
== Development
|
45
123
|
|
@@ -49,13 +127,16 @@ To open a console with the gem loaded, run `bundle console`.
|
|
49
127
|
|
50
128
|
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
129
|
|
130
|
+
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`.
|
131
|
+
To test on all rubies using docker, you can use `bundle exec rake test-all`.
|
132
|
+
|
52
133
|
== Feedback and success stories
|
53
134
|
|
54
135
|
Your feedback is welcome!
|
55
136
|
|
56
137
|
== Contributing
|
57
138
|
|
58
|
-
Bug reports and pull requests are welcome
|
139
|
+
Bug reports and pull requests are welcome at https://github.com/ivoanjo/backtracie.
|
59
140
|
|
60
141
|
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
142
|
|
@@ -64,3 +145,27 @@ Maintained with ❤️ by https://ivoanjo.me/[Ivo Anjo].
|
|
64
145
|
== Code of Conduct
|
65
146
|
|
66
147
|
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].
|
148
|
+
|
149
|
+
== Interesting Links
|
150
|
+
|
151
|
+
Here's some gems that are doing similar things to `backtracie`:
|
152
|
+
|
153
|
+
* https://github.com/tmm1/stackprof: A sampling call-stack profiler for Ruby
|
154
|
+
* https://github.com/ko1/pretty_backtrace: Pretty your exception backtrace
|
155
|
+
* 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
|
156
|
+
|
157
|
+
Other interesting links on this matter:
|
158
|
+
|
159
|
+
* https://github.com/ruby/ruby/pull/3299: vm_backtrace.c: let rb_profile_frames show cfunc frames
|
160
|
+
* https://github.com/ruby/ruby/pull/2713: Fix use of the rb_profile_frames start parameter
|
161
|
+
* 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.
|
162
|
+
* https://github.com/ko1/rubyhackchallenge: "Ruby Hack Challenge" (RHC) is a short guide to hack MRI (Matz Ruby Interpreter) internals
|
163
|
+
* https://docs.ruby-lang.org/en/3.0.0/doc/extension_rdoc.html: Creating Extension Libraries for Ruby
|
164
|
+
* https://ruby-hacking-guide.github.io/: Ruby Hacking Guide
|
165
|
+
** This is one of the most through and deep guides out there to the MRI internals. Very detailed and in depth, but outdated.
|
166
|
+
* http://patshaughnessy.net/ruby-under-a-microscope: Book with a really good introduction to MRI internals.
|
167
|
+
|
168
|
+
My blog posts on better backtraces:
|
169
|
+
|
170
|
+
* https://ivoanjo.me/blog/2020/07/05/ruby-experiment-include-class-names-in-backtraces/: ruby experiment: include class names in backtraces
|
171
|
+
* 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,21 @@ 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"]
|
49
|
+
|
50
|
+
# Enables support for Ruby 2.5 and below. Not used at all in the others.
|
51
|
+
spec.add_dependency "debase-ruby_core_source", "~> 0.10", ">= 0.10.12"
|
43
52
|
end
|
@@ -16,44 +16,52 @@
|
|
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;
|
32
36
|
static VALUE backtracie_module = Qnil;
|
33
37
|
static VALUE backtracie_location_class = Qnil;
|
34
38
|
|
35
|
-
// Function headers
|
36
|
-
|
37
39
|
static VALUE primitive_caller_locations(VALUE self);
|
38
40
|
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
|
-
|
41
|
+
static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames);
|
42
|
+
inline static VALUE new_location(VALUE absolute_path, VALUE base_label, VALUE label, VALUE lineno, VALUE path, VALUE qualified_method_name, VALUE debug);
|
43
|
+
static VALUE ruby_frame_to_location(raw_location *the_location);
|
44
|
+
static VALUE qualified_method_name_for_location(raw_location *the_location);
|
45
|
+
static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location);
|
46
|
+
static VALUE frame_from_location(raw_location *the_location);
|
47
|
+
static VALUE qualified_method_name_for_block(raw_location *the_location);
|
48
|
+
static VALUE qualified_method_name_from_self(raw_location *the_location);
|
49
|
+
static bool is_self_class_singleton(raw_location *the_location);
|
50
|
+
static bool is_defined_class_a_refinement(raw_location *the_location);
|
51
|
+
static VALUE debug_raw_location(raw_location *the_location);
|
52
|
+
static VALUE debug_frame(VALUE frame);
|
53
|
+
static inline VALUE to_boolean(bool value);
|
49
54
|
|
50
55
|
void Init_backtracie_native_extension(void) {
|
56
|
+
main_object_instance = rb_funcall(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), rb_intern("eval"), 1, rb_str_new2("self"));
|
57
|
+
ensure_object_is_thread_id = rb_intern("ensure_object_is_thread");
|
58
|
+
to_s_id = rb_intern("to_s");
|
59
|
+
|
51
60
|
backtracie_module = rb_const_get(rb_cObject, rb_intern("Backtracie"));
|
52
61
|
rb_global_variable(&backtracie_module);
|
53
62
|
|
54
63
|
rb_define_module_function(backtracie_module, "backtrace_locations", primitive_backtrace_locations, 1);
|
55
64
|
|
56
|
-
// We need to keep a reference to Backtracie::Locations around, to create new instances
|
57
65
|
backtracie_location_class = rb_const_get(backtracie_module, rb_intern("Location"));
|
58
66
|
rb_global_variable(&backtracie_location_class);
|
59
67
|
|
@@ -63,47 +71,34 @@ void Init_backtracie_native_extension(void) {
|
|
63
71
|
}
|
64
72
|
|
65
73
|
// Get array of Backtracie::Locations for a given thread; if thread is nil, returns for the current thread
|
66
|
-
static VALUE
|
74
|
+
static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames) {
|
67
75
|
int stack_depth = 0;
|
68
|
-
|
69
|
-
VALUE correct_labels[MAX_STACK_DEPTH];
|
70
|
-
int lines[MAX_STACK_DEPTH];
|
76
|
+
raw_location raw_locations[MAX_STACK_DEPTH];
|
71
77
|
|
72
78
|
if (thread == Qnil) {
|
73
79
|
// Get for own thread
|
74
|
-
stack_depth =
|
80
|
+
stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
|
75
81
|
} else {
|
76
|
-
stack_depth =
|
82
|
+
stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
|
83
|
+
if (stack_depth == 0 && !backtracie_is_thread_alive(thread)) return Qnil;
|
77
84
|
}
|
78
85
|
|
79
|
-
// Ignore the last frame -- seems to be an uninteresting VM frame. MRI itself seems to ignore the last frame in
|
80
|
-
// the implementation of backtrace_collect()
|
81
|
-
int ignored_stack_bottom_frames = 1;
|
82
|
-
|
83
|
-
stack_depth -= ignored_stack_bottom_frames;
|
84
|
-
|
85
86
|
VALUE locations = rb_ary_new_capa(stack_depth - ignored_stack_top_frames);
|
86
87
|
|
87
88
|
// MRI does not give us the path or line number for frames implemented using C code. The convention in
|
88
89
|
// Kernel#caller_locations is to instead use the path and line number of the last Ruby frame seen.
|
89
90
|
// Thus, we keep that frame here to able to replicate that behavior.
|
90
91
|
// (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;
|
92
|
+
raw_location *last_ruby_location = 0;
|
93
93
|
|
94
94
|
for (int i = stack_depth - 1; i >= ignored_stack_top_frames; i--) {
|
95
|
-
VALUE frame = frames[i];
|
96
|
-
int line = lines[i];
|
97
|
-
|
98
95
|
VALUE location = Qnil;
|
99
96
|
|
100
|
-
if (is_ruby_frame
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
location = ruby_frame_to_location(frame, last_ruby_line, correct_labels[i]);
|
97
|
+
if (raw_locations[i].is_ruby_frame) {
|
98
|
+
last_ruby_location = &raw_locations[i];
|
99
|
+
location = ruby_frame_to_location(&raw_locations[i]);
|
105
100
|
} else {
|
106
|
-
location = cfunc_frame_to_location(
|
101
|
+
location = cfunc_frame_to_location(&raw_locations[i], last_ruby_location);
|
107
102
|
}
|
108
103
|
|
109
104
|
rb_ary_store(locations, i - ignored_stack_top_frames, location);
|
@@ -119,67 +114,232 @@ static VALUE primitive_caller_locations(VALUE self) {
|
|
119
114
|
// * the frame from the caller itself (since we're replicating the semantics of Kernel#caller_locations)
|
120
115
|
int ignored_stack_top_frames = 3;
|
121
116
|
|
122
|
-
return
|
117
|
+
return collect_backtrace_locations(self, Qnil, ignored_stack_top_frames);
|
123
118
|
}
|
124
119
|
|
125
120
|
static VALUE primitive_backtrace_locations(VALUE self, VALUE thread) {
|
126
|
-
rb_funcall(backtracie_module,
|
121
|
+
rb_funcall(backtracie_module, ensure_object_is_thread_id, 1, thread);
|
127
122
|
|
128
123
|
int ignored_stack_top_frames = 0;
|
129
124
|
|
130
|
-
return
|
125
|
+
return collect_backtrace_locations(self, thread, ignored_stack_top_frames);
|
131
126
|
}
|
132
127
|
|
133
|
-
inline static VALUE new_location(
|
134
|
-
VALUE
|
128
|
+
inline static VALUE new_location(
|
129
|
+
VALUE absolute_path,
|
130
|
+
VALUE base_label,
|
131
|
+
VALUE label,
|
132
|
+
VALUE lineno,
|
133
|
+
VALUE path,
|
134
|
+
VALUE qualified_method_name,
|
135
|
+
VALUE debug
|
136
|
+
) {
|
137
|
+
VALUE arguments[] = { absolute_path, base_label, label, lineno, path, qualified_method_name, debug };
|
135
138
|
return rb_class_new_instance(VALUE_COUNT(arguments), arguments, backtracie_location_class);
|
136
139
|
}
|
137
140
|
|
138
|
-
static
|
139
|
-
VALUE
|
141
|
+
static VALUE ruby_frame_to_location(raw_location *the_location) {
|
142
|
+
VALUE frame = frame_from_location(the_location);
|
140
143
|
|
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
144
|
return new_location(
|
147
145
|
rb_profile_frame_absolute_path(frame),
|
148
146
|
rb_profile_frame_base_label(frame),
|
149
|
-
rb_profile_frame_label(
|
150
|
-
|
147
|
+
rb_profile_frame_label(the_location->iseq),
|
148
|
+
INT2FIX(the_location->line_number),
|
151
149
|
rb_profile_frame_path(frame),
|
152
|
-
|
150
|
+
qualified_method_name_for_location(the_location),
|
151
|
+
debug_raw_location(the_location)
|
153
152
|
);
|
154
153
|
}
|
155
154
|
|
156
|
-
static VALUE
|
157
|
-
VALUE
|
155
|
+
static VALUE qualified_method_name_for_location(raw_location *the_location) {
|
156
|
+
VALUE frame = frame_from_location(the_location);
|
157
|
+
VALUE defined_class = backtracie_defined_class(the_location);
|
158
|
+
VALUE qualified_method_name = Qnil;
|
159
|
+
|
160
|
+
if (is_defined_class_a_refinement(the_location)) {
|
161
|
+
qualified_method_name = backtracie_refinement_name(the_location);
|
162
|
+
rb_str_concat(qualified_method_name, rb_str_new2("#"));
|
163
|
+
rb_str_concat(qualified_method_name, rb_profile_frame_label(frame));
|
164
|
+
} else if (is_self_class_singleton(the_location)) {
|
165
|
+
qualified_method_name = qualified_method_name_from_self(the_location);
|
166
|
+
} else if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
167
|
+
qualified_method_name = qualified_method_name_for_block(the_location);
|
168
|
+
} else if (defined_class != Qnil && rb_mod_name(defined_class) == Qnil) {
|
169
|
+
// Instance of an anonymous class. Let's find it a name
|
170
|
+
VALUE superclass = defined_class;
|
171
|
+
VALUE superclass_name = Qnil;
|
172
|
+
do {
|
173
|
+
superclass = RCLASS_SUPER(superclass);
|
174
|
+
superclass_name = rb_mod_name(superclass);
|
175
|
+
} while (superclass_name == Qnil);
|
176
|
+
|
177
|
+
qualified_method_name = rb_str_new2("");
|
178
|
+
rb_str_concat(qualified_method_name, superclass_name);
|
179
|
+
rb_str_concat(qualified_method_name, rb_str_new2("$anonymous#"));
|
180
|
+
rb_str_concat(qualified_method_name, rb_profile_frame_base_label(frame_from_location(the_location)));
|
181
|
+
} else {
|
182
|
+
qualified_method_name = backtracie_rb_profile_frame_qualified_method_name(frame);
|
183
|
+
|
184
|
+
if (qualified_method_name == Qnil) {
|
185
|
+
qualified_method_name = qualified_method_name_from_self(the_location);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
190
|
+
rb_str_concat(qualified_method_name, rb_str_new2("{block}"));
|
191
|
+
}
|
192
|
+
|
193
|
+
return qualified_method_name;
|
194
|
+
}
|
195
|
+
|
196
|
+
static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location) {
|
197
|
+
VALUE last_ruby_frame =
|
198
|
+
last_ruby_location != 0 ? frame_from_location(last_ruby_location) : Qnil;
|
199
|
+
|
200
|
+
// Replaces label and base_label in cfuncs
|
201
|
+
VALUE method_name = backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
|
158
202
|
|
159
203
|
return new_location(
|
160
|
-
|
204
|
+
SAFE_NAVIGATION(rb_profile_frame_absolute_path, last_ruby_frame),
|
161
205
|
method_name,
|
162
206
|
method_name,
|
163
|
-
|
164
|
-
|
165
|
-
|
207
|
+
last_ruby_location != 0 ? INT2FIX(last_ruby_location->line_number) : Qnil,
|
208
|
+
SAFE_NAVIGATION(rb_profile_frame_path, last_ruby_frame),
|
209
|
+
backtracie_rb_profile_frame_qualified_method_name(the_location->callable_method_entry),
|
210
|
+
debug_raw_location(the_location)
|
166
211
|
);
|
167
212
|
}
|
168
213
|
|
169
|
-
|
170
|
-
|
214
|
+
static VALUE frame_from_location(raw_location *the_location) {
|
215
|
+
return \
|
216
|
+
the_location->should_use_iseq ||
|
217
|
+
// This one is somewhat weird, but the regular MRI Ruby APIs seem to pick the iseq for evals as well
|
218
|
+
backtracie_iseq_is_eval(the_location) ?
|
219
|
+
the_location->iseq : the_location->callable_method_entry;
|
220
|
+
}
|
221
|
+
|
222
|
+
static VALUE qualified_method_name_for_block(raw_location *the_location) {
|
223
|
+
VALUE class_name = backtracie_rb_profile_frame_classpath(the_location->callable_method_entry);
|
224
|
+
VALUE method_name = backtracie_called_id(the_location);
|
225
|
+
VALUE is_singleton_method = rb_profile_frame_singleton_method_p(the_location->iseq);
|
226
|
+
|
227
|
+
VALUE name = rb_str_new2("");
|
228
|
+
rb_str_concat(name, class_name);
|
229
|
+
rb_str_concat(name, is_singleton_method ? rb_str_new2(".") : rb_str_new2("#"));
|
230
|
+
rb_str_concat(name, rb_sym2str(method_name));
|
231
|
+
|
232
|
+
return name;
|
233
|
+
}
|
234
|
+
|
235
|
+
static VALUE qualified_method_name_from_self(raw_location *the_location) {
|
236
|
+
if (the_location->self == Qnil) return Qnil;
|
237
|
+
|
238
|
+
VALUE self_class = rb_class_of(the_location->self);
|
239
|
+
bool is_self_class_singleton = FL_TEST(self_class, FL_SINGLETON);
|
240
|
+
|
241
|
+
VALUE name = rb_str_new2("");
|
242
|
+
if (is_self_class_singleton) {
|
243
|
+
if (the_location->self == main_object_instance) {
|
244
|
+
rb_str_concat(name, rb_str_new2("Object$<main>#"));
|
245
|
+
} else {
|
246
|
+
// Crawl up the hierarchy to find a real class
|
247
|
+
VALUE the_class = rb_class_real(self_class);
|
248
|
+
|
249
|
+
// This is similar to BetterBacktraceHelper's self_class_module_or_class?
|
250
|
+
// If the real class of this object is the actual class Class or the class Module,
|
251
|
+
// it means that this is a method being directly called on a given Class/Module,
|
252
|
+
// e.g. Kernel.puts or BasicObject.name. In this case, Ruby already sets the name
|
253
|
+
// correctly, so we just delegate.
|
254
|
+
if (the_class == rb_cModule || the_class == rb_cClass) {
|
255
|
+
// Is the class/module is anonymous?
|
256
|
+
if (rb_mod_name(the_location->self) == Qnil) {
|
257
|
+
rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
|
258
|
+
rb_str_concat(name, rb_str_new2("$singleton."));
|
259
|
+
} else {
|
260
|
+
return SAFE_NAVIGATION(backtracie_rb_profile_frame_qualified_method_name, the_location->callable_method_entry);
|
261
|
+
}
|
262
|
+
} else {
|
263
|
+
rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
|
264
|
+
rb_str_concat(name, rb_str_new2("$singleton#"));
|
265
|
+
}
|
266
|
+
}
|
267
|
+
} else {
|
268
|
+
// Not very sure if this branch of the if is ever reached, and if it would be for a instance or static call, so
|
269
|
+
// let's just have these defaults and revisit as needed
|
270
|
+
rb_str_concat(name, rb_funcall(self_class, to_s_id, 0));
|
271
|
+
rb_str_concat(name, rb_str_new2("#"));
|
272
|
+
}
|
273
|
+
|
274
|
+
if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
|
275
|
+
// Nothing to do, {block} will be appended in qualified_method_name_for_location which called us
|
276
|
+
} else {
|
277
|
+
rb_str_concat(name, rb_profile_frame_base_label(frame_from_location(the_location)));
|
278
|
+
}
|
279
|
+
|
280
|
+
return name;
|
281
|
+
}
|
282
|
+
|
283
|
+
static bool is_self_class_singleton(raw_location *the_location) {
|
284
|
+
return the_location->self != Qnil && FL_TEST(rb_class_of(the_location->self), FL_SINGLETON);
|
285
|
+
}
|
286
|
+
|
287
|
+
static bool is_defined_class_a_refinement(raw_location *the_location) {
|
288
|
+
VALUE defined_class = backtracie_defined_class(the_location);
|
289
|
+
|
290
|
+
return defined_class != Qnil && FL_TEST(rb_class_of(defined_class), RMODULE_IS_REFINEMENT);
|
291
|
+
}
|
292
|
+
|
293
|
+
static VALUE debug_raw_location(raw_location *the_location) {
|
294
|
+
VALUE self_class = SAFE_NAVIGATION(rb_class_of, the_location->self);
|
295
|
+
|
171
296
|
VALUE arguments[] = {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
297
|
+
ID2SYM(rb_intern("ruby_frame?")) , /* => */ to_boolean(the_location->is_ruby_frame),
|
298
|
+
ID2SYM(rb_intern("should_use_iseq")), /* => */ to_boolean(the_location->should_use_iseq),
|
299
|
+
ID2SYM(rb_intern("vm_method_type")), /* => */ INT2FIX(the_location->vm_method_type),
|
300
|
+
ID2SYM(rb_intern("line_number")), /* => */ INT2FIX(the_location->line_number),
|
301
|
+
ID2SYM(rb_intern("called_id")), /* => */ backtracie_called_id(the_location),
|
302
|
+
// TODO: On Ruby < 3.0, running be pry -e 'require "backtracie"; Backtracie.caller_locations' with this being
|
303
|
+
// exposed causes a VM segfault inside the pretty printing code
|
304
|
+
// ID2SYM(rb_intern("defined_class")), /* => */ backtracie_defined_class(the_location),
|
305
|
+
ID2SYM(rb_intern("defined_class_refinement?")), /* => */ to_boolean(is_defined_class_a_refinement(the_location)),
|
306
|
+
ID2SYM(rb_intern("self_class")), /* => */ self_class,
|
307
|
+
ID2SYM(rb_intern("real_class")), /* => */ SAFE_NAVIGATION(rb_class_real, self_class),
|
308
|
+
ID2SYM(rb_intern("self")), /* => */ the_location->self,
|
309
|
+
ID2SYM(rb_intern("self_class_singleton?")), /* => */ to_boolean(is_self_class_singleton(the_location)),
|
310
|
+
ID2SYM(rb_intern("iseq_is_block?")), /* => */ to_boolean(backtracie_iseq_is_block(the_location)),
|
311
|
+
ID2SYM(rb_intern("iseq_is_eval?")), /* => */ to_boolean(backtracie_iseq_is_eval(the_location)),
|
312
|
+
ID2SYM(rb_intern("iseq")), /* => */ debug_frame(the_location->iseq),
|
313
|
+
ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry),
|
314
|
+
ID2SYM(rb_intern("original_id")), /* => */ the_location->original_id
|
183
315
|
};
|
184
|
-
|
316
|
+
|
317
|
+
VALUE debug_hash = rb_hash_new();
|
318
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
|
319
|
+
return debug_hash;
|
320
|
+
}
|
321
|
+
|
322
|
+
static VALUE debug_frame(VALUE frame) {
|
323
|
+
if (frame == Qnil) return Qnil;
|
324
|
+
|
325
|
+
VALUE arguments[] = {
|
326
|
+
ID2SYM(rb_intern("path")), /* => */ rb_profile_frame_path(frame),
|
327
|
+
ID2SYM(rb_intern("absolute_path")), /* => */ rb_profile_frame_absolute_path(frame),
|
328
|
+
ID2SYM(rb_intern("label")), /* => */ rb_profile_frame_label(frame),
|
329
|
+
ID2SYM(rb_intern("base_label")), /* => */ rb_profile_frame_base_label(frame),
|
330
|
+
ID2SYM(rb_intern("full_label")), /* => */ rb_profile_frame_full_label(frame),
|
331
|
+
ID2SYM(rb_intern("first_lineno")), /* => */ rb_profile_frame_first_lineno(frame),
|
332
|
+
ID2SYM(rb_intern("classpath")), /* => */ backtracie_rb_profile_frame_classpath(frame),
|
333
|
+
ID2SYM(rb_intern("singleton_method_p")), /* => */ rb_profile_frame_singleton_method_p(frame),
|
334
|
+
ID2SYM(rb_intern("method_name")), /* => */ rb_profile_frame_method_name(frame),
|
335
|
+
ID2SYM(rb_intern("qualified_method_name")), /* => */ backtracie_rb_profile_frame_qualified_method_name(frame)
|
336
|
+
};
|
337
|
+
|
338
|
+
VALUE debug_hash = rb_hash_new();
|
339
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
|
340
|
+
return debug_hash;
|
341
|
+
}
|
342
|
+
|
343
|
+
static inline VALUE to_boolean(bool value) {
|
344
|
+
return value ? Qtrue : Qfalse;
|
185
345
|
}
|