backtracie 0.3.0 → 1.1.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/README.adoc +3 -0
- data/ext/backtracie_native_extension/backtracie.c +182 -245
- data/ext/backtracie_native_extension/backtracie_frames.c +941 -0
- data/ext/backtracie_native_extension/backtracie_private.h +20 -0
- data/ext/backtracie_native_extension/c_test_helpers.c +62 -0
- data/ext/backtracie_native_extension/extconf.rb +11 -17
- data/ext/backtracie_native_extension/public/backtracie.h +268 -0
- data/ext/backtracie_native_extension/strbuilder.c +123 -0
- data/ext/backtracie_native_extension/strbuilder.h +23 -0
- data/lib/backtracie/location.rb +6 -1
- data/lib/backtracie/version.rb +1 -1
- metadata +9 -5
- data/ext/backtracie_native_extension/ruby_shards.c +0 -533
- data/ext/backtracie_native_extension/ruby_shards.h +0 -170
@@ -1,533 +0,0 @@
|
|
1
|
-
// backtracie: Ruby gem for beautiful backtraces
|
2
|
-
// Copyright (C) 2021 Ivo Anjo <ivo@ivoanjo.me>
|
3
|
-
//
|
4
|
-
// This file is part of backtracie.
|
5
|
-
//
|
6
|
-
// backtracie is free software: you can redistribute it and/or modify
|
7
|
-
// it under the terms of the GNU Lesser General Public License as published by
|
8
|
-
// the Free Software Foundation, either version 3 of the License, or
|
9
|
-
// (at your option) any later version.
|
10
|
-
//
|
11
|
-
// backtracie is distributed in the hope that it will be useful,
|
12
|
-
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
-
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
-
// GNU Lesser General Public License for more details.
|
15
|
-
//
|
16
|
-
// You should have received a copy of the GNU Lesser General Public License
|
17
|
-
// along with backtracie. If not, see <http://www.gnu.org/licenses/>.
|
18
|
-
|
19
|
-
// -----------------------------------------------------------------------------
|
20
|
-
// The file below has modified versions of code extracted from the Ruby project.
|
21
|
-
// The Ruby project copyright and license follow:
|
22
|
-
// -----------------------------------------------------------------------------
|
23
|
-
|
24
|
-
// Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
25
|
-
// You can redistribute it and/or modify it under either the terms of the
|
26
|
-
// 2-clause BSDL (see the file BSDL), or the conditions below:
|
27
|
-
|
28
|
-
// 1. You may make and give away verbatim copies of the source form of the
|
29
|
-
// software without restriction, provided that you duplicate all of the
|
30
|
-
// original copyright notices and associated disclaimers.
|
31
|
-
|
32
|
-
// 2. You may modify your copy of the software in any way, provided that
|
33
|
-
// you do at least ONE of the following:
|
34
|
-
|
35
|
-
// a. place your modifications in the Public Domain or otherwise
|
36
|
-
// make them Freely Available, such as by posting said
|
37
|
-
// modifications to Usenet or an equivalent medium, or by allowing
|
38
|
-
// the author to include your modifications in the software.
|
39
|
-
|
40
|
-
// b. use the modified software only within your corporation or
|
41
|
-
// organization.
|
42
|
-
|
43
|
-
// c. give non-standard binaries non-standard names, with
|
44
|
-
// instructions on where to get the original software distribution.
|
45
|
-
|
46
|
-
// d. make other distribution arrangements with the author.
|
47
|
-
|
48
|
-
// 3. You may distribute the software in object code or binary form,
|
49
|
-
// provided that you do at least ONE of the following:
|
50
|
-
|
51
|
-
// a. distribute the binaries and library files of the software,
|
52
|
-
// together with instructions (in the manual page or equivalent)
|
53
|
-
// on where to get the original distribution.
|
54
|
-
|
55
|
-
// b. accompany the distribution with the machine-readable source of
|
56
|
-
// the software.
|
57
|
-
|
58
|
-
// c. give non-standard binaries non-standard names, with
|
59
|
-
// instructions on where to get the original software distribution.
|
60
|
-
|
61
|
-
// d. make other distribution arrangements with the author.
|
62
|
-
|
63
|
-
// 4. You may modify and include the part of the software into any other
|
64
|
-
// software (possibly commercial). But some files in the distribution
|
65
|
-
// are not written by the author, so that they are not under these terms.
|
66
|
-
|
67
|
-
// For the list of those files and their copying conditions, see the
|
68
|
-
// file LEGAL.
|
69
|
-
|
70
|
-
// 5. The scripts and library files supplied as input to or produced as
|
71
|
-
// output from the software do not automatically fall under the
|
72
|
-
// copyright of the software, but belong to whomever generated them,
|
73
|
-
// and may be sold commercially, and may be aggregated with this
|
74
|
-
// software.
|
75
|
-
|
76
|
-
// 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
77
|
-
// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
78
|
-
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
79
|
-
// PURPOSE.
|
80
|
-
|
81
|
-
// -----------------------------------------------------------------------------
|
82
|
-
|
83
|
-
// ruby_shards.c contains a number of borrowed functions from the MRI Ruby (usually 3.0.0) source tree:
|
84
|
-
// * A few were copy-pasted verbatim, and are dependencies for other functions
|
85
|
-
// * A few were copy-pasted and then changed so we can add features and fixes
|
86
|
-
// * A few were copy-pasted verbatim, with the objective of backporting their 3.0.0 behavior to earlier Ruby versions
|
87
|
-
// `git blame` usually documents which functions were added for what reason.
|
88
|
-
|
89
|
-
// Note that since the RUBY_MJIT_HEADER is a very special header, meant for internal use only, it has a number of quirks:
|
90
|
-
//
|
91
|
-
// 1. I've seen a few segfaults when trying to call back into original Ruby functions. E.g. even if the API is used
|
92
|
-
// correctly, just the mere inclusion of RUBY_MJIT_HEADER causes usage to crash. Thus, as much as possible, it's
|
93
|
-
// better to define functions OUTSIDE this file.
|
94
|
-
//
|
95
|
-
// 2. On Windows, I've observed "multiple definition of `something...'" (such as `rb_vm_ep_local_ep') whenever there
|
96
|
-
// are multiple files in the codebase that include the RUBY_MJIT_HEADER.
|
97
|
-
// It looks like (some?) Windows versions of Ruby define a bunch of functions in the RUBY_MJIT_HEADER itself
|
98
|
-
// without marking them as "static" (e.g. not visible to the outside of the file), and thus the linker then complains
|
99
|
-
// when linking together several files which all have these non-private symbols.
|
100
|
-
// One possible hacky solution suggested by the internets is to use the "-Wl,-allow-multiple-definition" linker
|
101
|
-
// flags to ignore this problem; instead I've chosen to implement all usage of the RUBY_MJIT_HEADER on this file --
|
102
|
-
// no other file in backtracie shall include RUBY_MJIT_HEADER.
|
103
|
-
// It's a simpler approach, and hopefully avoids any problems.
|
104
|
-
|
105
|
-
#include "extconf.h"
|
106
|
-
|
107
|
-
#ifndef PRE_MJIT_RUBY
|
108
|
-
#ifndef RUBY_MJIT_HEADER_INCLUDED
|
109
|
-
#define RUBY_MJIT_HEADER_INCLUDED
|
110
|
-
#include RUBY_MJIT_HEADER
|
111
|
-
#endif
|
112
|
-
#endif
|
113
|
-
|
114
|
-
#include "ruby_shards.h"
|
115
|
-
|
116
|
-
#ifdef PRE_MJIT_RUBY
|
117
|
-
#include <iseq.h>
|
118
|
-
#include <regenc.h>
|
119
|
-
#endif
|
120
|
-
|
121
|
-
#ifdef PRE_EXECUTION_CONTEXT
|
122
|
-
// The thread and its execution context were separated on Ruby 2.5; prior to that, everything was part of the thread
|
123
|
-
#define rb_execution_context_t rb_thread_t
|
124
|
-
#endif
|
125
|
-
|
126
|
-
#ifdef PRE_VM_ENV_RENAMES
|
127
|
-
#define VM_ENV_LOCAL_P VM_EP_LEP_P
|
128
|
-
#define VM_ENV_PREV_EP VM_EP_PREV_EP
|
129
|
-
#define VM_ENV_DATA_INDEX_ME_CREF -1
|
130
|
-
#define VM_FRAME_RUBYFRAME_P(cfp) RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)
|
131
|
-
#endif
|
132
|
-
|
133
|
-
/**********************************************************************
|
134
|
-
vm_backtrace.c -
|
135
|
-
$Author: ko1 $
|
136
|
-
created at: Sun Jun 03 00:14:20 2012
|
137
|
-
Copyright (C) 1993-2012 Yukihiro Matsumoto
|
138
|
-
**********************************************************************/
|
139
|
-
|
140
|
-
inline static int
|
141
|
-
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
|
142
|
-
{
|
143
|
-
VM_ASSERT(iseq);
|
144
|
-
VM_ASSERT(iseq->body);
|
145
|
-
VM_ASSERT(iseq->body->iseq_encoded);
|
146
|
-
VM_ASSERT(iseq->body->iseq_size);
|
147
|
-
if (! pc) {
|
148
|
-
/* This can happen during VM bootup. */
|
149
|
-
VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
|
150
|
-
VM_ASSERT(! iseq->body->local_table);
|
151
|
-
VM_ASSERT(! iseq->body->local_table_size);
|
152
|
-
return 0;
|
153
|
-
}
|
154
|
-
else {
|
155
|
-
ptrdiff_t n = pc - iseq->body->iseq_encoded;
|
156
|
-
VM_ASSERT(n <= iseq->body->iseq_size);
|
157
|
-
VM_ASSERT(n >= 0);
|
158
|
-
ASSUME(n >= 0);
|
159
|
-
size_t pos = n; /* no overflow */
|
160
|
-
if (LIKELY(pos)) {
|
161
|
-
/* use pos-1 because PC points next instruction at the beginning of instruction */
|
162
|
-
pos--;
|
163
|
-
}
|
164
|
-
return rb_iseq_line_no(iseq, pos);
|
165
|
-
}
|
166
|
-
}
|
167
|
-
|
168
|
-
static VALUE
|
169
|
-
id2str(ID id)
|
170
|
-
{
|
171
|
-
VALUE str = rb_id2str(id);
|
172
|
-
if (!str) return Qnil;
|
173
|
-
return str;
|
174
|
-
}
|
175
|
-
#define rb_id2str(id) id2str(id)
|
176
|
-
|
177
|
-
// Hacked version of Ruby's rb_profile_frames from Ruby 3.0.0 with the following changes:
|
178
|
-
// 1. Instead of just using the rb_execution_context_t for the current thread, the context is received as an argument,
|
179
|
-
// thus allowing the sampling of any thread in the VM, not just the current one.
|
180
|
-
// 2. It gathers a lot more data: originally you'd get only a VALUE (either iseq or the cme, depending on the case),
|
181
|
-
// and the line number. The hacked version returns a whole raw_location with a lot more info.
|
182
|
-
// 3. It correctly ignores the dummy frame at the bottom of the main Ruby thread stack, thus mimicking the behavior of
|
183
|
-
// Ruby's backtrace_each (which is the function that is used to implement Thread#backtrace and friends)
|
184
|
-
// 4. Removed the start argument (upstream was broken anyway -- https://github.com/ruby/ruby/pull/2713 -- so we can
|
185
|
-
// re-add later if needed)
|
186
|
-
static int backtracie_rb_profile_frames_for_execution_context(
|
187
|
-
rb_execution_context_t *ec,
|
188
|
-
int limit,
|
189
|
-
raw_location *raw_locations
|
190
|
-
) {
|
191
|
-
int i = 0;
|
192
|
-
const rb_control_frame_t *cfp = ec->cfp;
|
193
|
-
const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
|
194
|
-
const rb_callable_method_entry_t *cme = 0;
|
195
|
-
|
196
|
-
// Hack #3 above: Here we go back one frame in addition to what the original Ruby rb_profile_frames method did.
|
197
|
-
// Why? According to backtrace_each() in vm_backtrace.c there's two "dummy frames" (what MRI calls them) at the
|
198
|
-
// bottom of the stack, and we need to skip them both.
|
199
|
-
// I have no idea why the original rb_profile_frames omits this. Without this, sampling `Thread.main` always
|
200
|
-
// returned one more frame than the regular MRI APIs (which use the aforementioned backtrace_each internally).
|
201
|
-
end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
|
202
|
-
|
203
|
-
for (i = 0; i < limit && cfp != end_cfp;) {
|
204
|
-
// Initialize the raw_location, to avoid issues
|
205
|
-
raw_locations[i].is_ruby_frame = false;
|
206
|
-
raw_locations[i].should_use_iseq = false;
|
207
|
-
raw_locations[i].vm_method_type = 0;
|
208
|
-
raw_locations[i].line_number = 0;
|
209
|
-
raw_locations[i].iseq = Qnil;
|
210
|
-
raw_locations[i].callable_method_entry = Qnil;
|
211
|
-
raw_locations[i].original_id = Qnil;
|
212
|
-
|
213
|
-
// The current object this is getting called on!
|
214
|
-
raw_locations[i].self = cfp->self;
|
215
|
-
|
216
|
-
cme = rb_vm_frame_method_entry(cfp);
|
217
|
-
|
218
|
-
if (cfp->iseq && !cfp->pc) {
|
219
|
-
// Do nothing -- this frame should not be used
|
220
|
-
// Bugfix: rb_profile_frames did not do this (skip a frame when there's no pc), but backtrace_each did, and that
|
221
|
-
// caused the "Backtracie.backtrace_locations when sampling a map from an enumerable returns the same number of items as the Ruby API"
|
222
|
-
// test to fail -- Backtracie returned one more frame than Ruby. I suspect that, as usual, this is yet another case where
|
223
|
-
// rb_profile_frames fails us.
|
224
|
-
} else if (VM_FRAME_RUBYFRAME_P(cfp)) {
|
225
|
-
raw_locations[i].is_ruby_frame = true;
|
226
|
-
raw_locations[i].iseq = (VALUE) cfp->iseq;
|
227
|
-
|
228
|
-
if (cme) {
|
229
|
-
raw_locations[i].callable_method_entry = (VALUE) cme;
|
230
|
-
raw_locations[i].vm_method_type = cme->def->type;
|
231
|
-
}
|
232
|
-
|
233
|
-
if (!(cme && cme->def->type == VM_METHOD_TYPE_ISEQ)) {
|
234
|
-
// This comes from the original rb_profile_frames logic, which would only return the iseq when the cme
|
235
|
-
// type is not VM_METHOD_TYPE_ISEQ
|
236
|
-
raw_locations[i].should_use_iseq = true;
|
237
|
-
}
|
238
|
-
|
239
|
-
raw_locations[i].line_number = calc_lineno(cfp->iseq, cfp->pc);
|
240
|
-
|
241
|
-
i++;
|
242
|
-
} else {
|
243
|
-
if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
|
244
|
-
raw_locations[i].is_ruby_frame = false;
|
245
|
-
raw_locations[i].callable_method_entry = (VALUE) cme;
|
246
|
-
raw_locations[i].vm_method_type = cme->def->type;
|
247
|
-
raw_locations[i].line_number = 0;
|
248
|
-
raw_locations[i].original_id = ID2SYM(cme->def->original_id);
|
249
|
-
|
250
|
-
i++;
|
251
|
-
}
|
252
|
-
}
|
253
|
-
|
254
|
-
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
|
255
|
-
}
|
256
|
-
|
257
|
-
return i;
|
258
|
-
}
|
259
|
-
|
260
|
-
int backtracie_rb_profile_frames(int limit, raw_location *raw_locations) {
|
261
|
-
#ifndef PRE_EXECUTION_CONTEXT
|
262
|
-
return backtracie_rb_profile_frames_for_execution_context(GET_EC(), limit, raw_locations);
|
263
|
-
#else
|
264
|
-
// FIXME: Figure out how to make GET_EC (GET_THREAD) work for Ruby <= 2.4
|
265
|
-
return 0;
|
266
|
-
#endif
|
267
|
-
}
|
268
|
-
|
269
|
-
bool backtracie_is_thread_alive(VALUE thread) {
|
270
|
-
// In here we're assuming that what we got is really a Thread or its subclass. This assumption NEEDS to be verified by
|
271
|
-
// the caller, otherwise I see a segfault in your future.
|
272
|
-
rb_thread_t *thread_pointer = (rb_thread_t*) DATA_PTR(thread);
|
273
|
-
|
274
|
-
return !(thread_pointer->to_kill || thread_pointer->status == THREAD_KILLED);
|
275
|
-
}
|
276
|
-
|
277
|
-
int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_location *raw_locations) {
|
278
|
-
if (!backtracie_is_thread_alive(thread)) return 0;
|
279
|
-
|
280
|
-
// In here we're assuming that what we got is really a Thread or its subclass. This assumption NEEDS to be verified by
|
281
|
-
// the caller, otherwise I see a segfault in your future.
|
282
|
-
rb_thread_t *thread_pointer = (rb_thread_t*) DATA_PTR(thread);
|
283
|
-
|
284
|
-
#ifndef PRE_EXECUTION_CONTEXT
|
285
|
-
return backtracie_rb_profile_frames_for_execution_context(thread_pointer->ec, limit, raw_locations);
|
286
|
-
#else
|
287
|
-
return backtracie_rb_profile_frames_for_execution_context(thread_pointer, limit, raw_locations);
|
288
|
-
#endif
|
289
|
-
}
|
290
|
-
|
291
|
-
VALUE backtracie_called_id(raw_location *the_location) {
|
292
|
-
if (the_location->callable_method_entry == Qnil) return Qnil;
|
293
|
-
|
294
|
-
return ID2SYM(
|
295
|
-
((rb_callable_method_entry_t *) the_location->callable_method_entry)
|
296
|
-
->called_id
|
297
|
-
);
|
298
|
-
}
|
299
|
-
|
300
|
-
VALUE backtracie_defined_class(raw_location *the_location) {
|
301
|
-
if (the_location->callable_method_entry == Qnil) return Qnil;
|
302
|
-
|
303
|
-
return \
|
304
|
-
((rb_callable_method_entry_t *) the_location->callable_method_entry)
|
305
|
-
->defined_class;
|
306
|
-
}
|
307
|
-
|
308
|
-
bool backtracie_iseq_is_block(raw_location *the_location) {
|
309
|
-
if (the_location->iseq == Qnil) return false;
|
310
|
-
|
311
|
-
return ((rb_iseq_t *) the_location->iseq)->body->type == ISEQ_TYPE_BLOCK;
|
312
|
-
}
|
313
|
-
|
314
|
-
bool backtracie_iseq_is_eval(raw_location *the_location) {
|
315
|
-
if (the_location->iseq == Qnil) return false;
|
316
|
-
|
317
|
-
return ((rb_iseq_t *) the_location->iseq)->body->type == ISEQ_TYPE_EVAL;
|
318
|
-
}
|
319
|
-
|
320
|
-
VALUE backtracie_refinement_name(raw_location *the_location) {
|
321
|
-
VALUE defined_class = backtracie_defined_class(the_location);
|
322
|
-
if (defined_class == Qnil) return Qnil;
|
323
|
-
|
324
|
-
VALUE refinement_module = rb_class_of(defined_class);
|
325
|
-
if (!FL_TEST(refinement_module, RMODULE_IS_REFINEMENT)) return Qnil;
|
326
|
-
|
327
|
-
// The below bits are inspired by Ruby's rb_mod_to_s(VALUE)
|
328
|
-
ID id_refined_class;
|
329
|
-
CONST_ID(id_refined_class, "__refined_class__");
|
330
|
-
VALUE refined_class = rb_attr_get(refinement_module, id_refined_class);
|
331
|
-
if (refined_class == Qnil) return Qnil;
|
332
|
-
|
333
|
-
VALUE result = rb_inspect(refined_class);
|
334
|
-
rb_str_concat(result, rb_str_new2("$refinement@"));
|
335
|
-
ID id_defined_at;
|
336
|
-
CONST_ID(id_defined_at, "__defined_at__");
|
337
|
-
rb_str_concat(result, rb_inspect(rb_attr_get(refinement_module, id_defined_at)));
|
338
|
-
|
339
|
-
return result;
|
340
|
-
}
|
341
|
-
|
342
|
-
// For more details on the objective of this backport, see the comments on ruby_shards.h
|
343
|
-
// This is used for Ruby < 3.0.0
|
344
|
-
#ifdef CFUNC_FRAMES_BACKPORT_NEEDED
|
345
|
-
|
346
|
-
static const rb_callable_method_entry_t *
|
347
|
-
cframe(VALUE frame)
|
348
|
-
{
|
349
|
-
if (frame == Qnil) return NULL;
|
350
|
-
|
351
|
-
if (RB_TYPE_P(frame, T_IMEMO)) {
|
352
|
-
switch (imemo_type(frame)) {
|
353
|
-
case imemo_ment:
|
354
|
-
{
|
355
|
-
const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
|
356
|
-
switch (cme->def->type) {
|
357
|
-
case VM_METHOD_TYPE_CFUNC:
|
358
|
-
return cme;
|
359
|
-
default:
|
360
|
-
return NULL;
|
361
|
-
}
|
362
|
-
}
|
363
|
-
default:
|
364
|
-
return NULL;
|
365
|
-
}
|
366
|
-
}
|
367
|
-
|
368
|
-
return NULL;
|
369
|
-
}
|
370
|
-
|
371
|
-
static const rb_iseq_t *
|
372
|
-
frame2iseq(VALUE frame)
|
373
|
-
{
|
374
|
-
if (frame == Qnil) return NULL;
|
375
|
-
|
376
|
-
if (RB_TYPE_P(frame, T_IMEMO)) {
|
377
|
-
switch (imemo_type(frame)) {
|
378
|
-
case imemo_iseq:
|
379
|
-
return (const rb_iseq_t *)frame;
|
380
|
-
case imemo_ment:
|
381
|
-
{
|
382
|
-
const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
|
383
|
-
switch (cme->def->type) {
|
384
|
-
case VM_METHOD_TYPE_ISEQ:
|
385
|
-
return cme->def->body.iseq.iseqptr;
|
386
|
-
default:
|
387
|
-
return NULL;
|
388
|
-
}
|
389
|
-
}
|
390
|
-
default:
|
391
|
-
break;
|
392
|
-
}
|
393
|
-
}
|
394
|
-
rb_bug("frame2iseq: unreachable");
|
395
|
-
}
|
396
|
-
|
397
|
-
VALUE
|
398
|
-
backported_rb_profile_frame_method_name(VALUE frame)
|
399
|
-
{
|
400
|
-
const rb_callable_method_entry_t *cme = cframe(frame);
|
401
|
-
if (cme) {
|
402
|
-
ID mid = cme->def->original_id;
|
403
|
-
return id2str(mid);
|
404
|
-
}
|
405
|
-
const rb_iseq_t *iseq = frame2iseq(frame);
|
406
|
-
return iseq ? rb_iseq_method_name(iseq) : Qnil;
|
407
|
-
}
|
408
|
-
|
409
|
-
#endif // CFUNC_FRAMES_BACKPORT_NEEDED
|
410
|
-
|
411
|
-
#ifdef CLASSPATH_BACKPORT_NEEDED
|
412
|
-
static VALUE
|
413
|
-
frame2klass(VALUE frame)
|
414
|
-
{
|
415
|
-
if (frame == Qnil) return Qnil;
|
416
|
-
|
417
|
-
if (RB_TYPE_P(frame, T_IMEMO)) {
|
418
|
-
const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
|
419
|
-
|
420
|
-
if (imemo_type(frame) == imemo_ment) {
|
421
|
-
return cme->defined_class;
|
422
|
-
}
|
423
|
-
}
|
424
|
-
return Qnil;
|
425
|
-
}
|
426
|
-
|
427
|
-
VALUE
|
428
|
-
backported_rb_profile_frame_classpath(VALUE frame)
|
429
|
-
{
|
430
|
-
VALUE klass = frame2klass(frame);
|
431
|
-
|
432
|
-
if (klass && !NIL_P(klass)) {
|
433
|
-
if (RB_TYPE_P(klass, T_ICLASS)) {
|
434
|
-
klass = RBASIC(klass)->klass;
|
435
|
-
}
|
436
|
-
else if (FL_TEST(klass, FL_SINGLETON)) {
|
437
|
-
klass = rb_ivar_get(klass, id__attached__);
|
438
|
-
if (!RB_TYPE_P(klass, T_CLASS) && !RB_TYPE_P(klass, T_MODULE))
|
439
|
-
return rb_sprintf("#<%s:%p>", rb_class2name(rb_obj_class(klass)), (void*)klass);
|
440
|
-
}
|
441
|
-
return rb_class_path(klass);
|
442
|
-
}
|
443
|
-
else {
|
444
|
-
return Qnil;
|
445
|
-
}
|
446
|
-
}
|
447
|
-
|
448
|
-
// Oddly enough, this method is on debug.h BUT NOT on the MJIT header. Since I've had
|
449
|
-
// crashes when trying to combine the MJIT header with the regular Ruby headers, let's
|
450
|
-
// just supply the declaration for this function, as otherwise the build seems to fail
|
451
|
-
// on macOS in CI.
|
452
|
-
VALUE rb_profile_frame_singleton_method_p(VALUE frame);
|
453
|
-
|
454
|
-
static VALUE
|
455
|
-
qualified_method_name(VALUE frame, VALUE method_name)
|
456
|
-
{
|
457
|
-
if (method_name != Qnil) {
|
458
|
-
VALUE classpath = backported_rb_profile_frame_classpath(frame);
|
459
|
-
VALUE singleton_p = rb_profile_frame_singleton_method_p(frame);
|
460
|
-
|
461
|
-
if (classpath != Qnil) {
|
462
|
-
return rb_sprintf("%"PRIsVALUE"%s%"PRIsVALUE,
|
463
|
-
classpath, singleton_p == Qtrue ? "." : "#", method_name);
|
464
|
-
}
|
465
|
-
else {
|
466
|
-
return method_name;
|
467
|
-
}
|
468
|
-
}
|
469
|
-
else {
|
470
|
-
return Qnil;
|
471
|
-
}
|
472
|
-
}
|
473
|
-
|
474
|
-
VALUE
|
475
|
-
backported_rb_profile_frame_qualified_method_name(VALUE frame)
|
476
|
-
{
|
477
|
-
VALUE method_name = backported_rb_profile_frame_method_name(frame);
|
478
|
-
|
479
|
-
return qualified_method_name(frame, method_name);
|
480
|
-
}
|
481
|
-
#endif // CLASSPATH_BACKPORT_NEEDED
|
482
|
-
|
483
|
-
#ifdef PRE_MJIT_RUBY
|
484
|
-
|
485
|
-
// A few extra functions copied from the Ruby sources, needed to support Ruby < 2.6
|
486
|
-
|
487
|
-
/**********************************************************************
|
488
|
-
vm_insnhelper.c - instruction helper functions.
|
489
|
-
$Author$
|
490
|
-
Copyright (C) 2007 Koichi Sasada
|
491
|
-
**********************************************************************/
|
492
|
-
|
493
|
-
static rb_callable_method_entry_t *
|
494
|
-
check_method_entry(VALUE obj, int can_be_svar)
|
495
|
-
{
|
496
|
-
if (obj == Qfalse) return NULL;
|
497
|
-
|
498
|
-
#if VM_CHECK_MODE > 0
|
499
|
-
if (!RB_TYPE_P(obj, T_IMEMO)) rb_bug("check_method_entry: unknown type: %s", rb_obj_info(obj));
|
500
|
-
#endif
|
501
|
-
|
502
|
-
switch (imemo_type(obj)) {
|
503
|
-
case imemo_ment:
|
504
|
-
return (rb_callable_method_entry_t *)obj;
|
505
|
-
case imemo_cref:
|
506
|
-
return NULL;
|
507
|
-
case imemo_svar:
|
508
|
-
if (can_be_svar) {
|
509
|
-
return check_method_entry(((struct vm_svar *)obj)->cref_or_me, FALSE);
|
510
|
-
}
|
511
|
-
default:
|
512
|
-
#if VM_CHECK_MODE > 0
|
513
|
-
rb_bug("check_method_entry: svar should not be there:");
|
514
|
-
#endif
|
515
|
-
return NULL;
|
516
|
-
}
|
517
|
-
}
|
518
|
-
|
519
|
-
const rb_callable_method_entry_t *
|
520
|
-
rb_vm_frame_method_entry(const rb_control_frame_t *cfp)
|
521
|
-
{
|
522
|
-
const VALUE *ep = cfp->ep;
|
523
|
-
rb_callable_method_entry_t *me;
|
524
|
-
|
525
|
-
while (!VM_ENV_LOCAL_P(ep)) {
|
526
|
-
if ((me = check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) return me;
|
527
|
-
ep = VM_ENV_PREV_EP(ep);
|
528
|
-
}
|
529
|
-
|
530
|
-
return check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
|
531
|
-
}
|
532
|
-
|
533
|
-
#endif
|