backtracie 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,11 +18,55 @@
18
18
  # You should have received a copy of the GNU Lesser General Public License
19
19
  # along with backtracie. If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
+ if ["jruby", "truffleruby"].include?(RUBY_ENGINE)
22
+ raise \
23
+ "\n#{"-" * 80}\nSorry! This gem is unsupported on #{RUBY_ENGINE}. Since it relies on a lot of guts of MRI Ruby, " \
24
+ "it's impossible to make a direct port.\n" \
25
+ "Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{"-" * 80}"
26
+ end
27
+
21
28
  require "mkmf"
22
29
 
23
30
  # This warning gets really annoying when we include the Ruby mjit header file,
24
31
  # let's omit it
25
32
  $CFLAGS << " " << "-Wno-unused-function"
26
33
 
34
+ # Really dislike the "define everything at the beginning of the function" thing, sorry!
35
+ $CFLAGS << " " << "-Wno-declaration-after-statement"
36
+
37
+ # Enable us to use """modern""" C
38
+ if RUBY_VERSION < "2.4"
39
+ $CFLAGS << " " << "-std=c99"
40
+ end
41
+
42
+ # On older Rubies, we need to enable a few backports. See cfunc_frames_backport.h for details.
43
+ if RUBY_VERSION < "3"
44
+ $defs << "-DCFUNC_FRAMES_BACKPORT_NEEDED"
45
+ end
46
+
47
+ # Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby 2.6
48
+ if RUBY_VERSION.start_with?("2.6")
49
+ $defs << "-DCLASSPATH_BACKPORT_NEEDED"
50
+ end
51
+
52
+ if RUBY_VERSION < "2.6"
53
+ $defs << "-DPRE_MJIT_RUBY"
54
+ end
55
+
27
56
  create_header
57
+
58
+ # The Ruby MJIT header is always (afaik?) suffixed with the exact RUBY version,
59
+ # including patch (e.g. 2.7.2). Thus, we add to the header file a definition
60
+ # containing the exact file, so that it can be used in a #include in the C code.
61
+ header_contents =
62
+ File.read($extconf_h)
63
+ .sub("#endif",
64
+ <<~EXTCONF_H.strip
65
+ #define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
66
+
67
+ #endif
68
+ EXTCONF_H
69
+ )
70
+ File.open($extconf_h, "w") { |file| file.puts(header_contents) }
71
+
28
72
  create_makefile "backtracie_native_extension"
@@ -0,0 +1,585 @@
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
+
109
+ #ifndef RUBY_MJIT_HEADER_INCLUDED
110
+ #define RUBY_MJIT_HEADER_INCLUDED
111
+ #include RUBY_MJIT_HEADER
112
+ #endif
113
+
114
+ #include "ruby_shards.h"
115
+
116
+ /**********************************************************************
117
+ vm_backtrace.c -
118
+ $Author: ko1 $
119
+ created at: Sun Jun 03 00:14:20 2012
120
+ Copyright (C) 1993-2012 Yukihiro Matsumoto
121
+ **********************************************************************/
122
+
123
+ inline static int
124
+ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
125
+ {
126
+ VM_ASSERT(iseq);
127
+ VM_ASSERT(iseq->body);
128
+ VM_ASSERT(iseq->body->iseq_encoded);
129
+ VM_ASSERT(iseq->body->iseq_size);
130
+ if (! pc) {
131
+ /* This can happen during VM bootup. */
132
+ VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
133
+ VM_ASSERT(! iseq->body->local_table);
134
+ VM_ASSERT(! iseq->body->local_table_size);
135
+ return 0;
136
+ }
137
+ else {
138
+ ptrdiff_t n = pc - iseq->body->iseq_encoded;
139
+ VM_ASSERT(n <= iseq->body->iseq_size);
140
+ VM_ASSERT(n >= 0);
141
+ ASSUME(n >= 0);
142
+ size_t pos = n; /* no overflow */
143
+ if (LIKELY(pos)) {
144
+ /* use pos-1 because PC points next instruction at the beginning of instruction */
145
+ pos--;
146
+ }
147
+ return rb_iseq_line_no(iseq, pos);
148
+ }
149
+ }
150
+
151
+ static VALUE
152
+ id2str(ID id)
153
+ {
154
+ VALUE str = rb_id2str(id);
155
+ if (!str) return Qnil;
156
+ return str;
157
+ }
158
+ #define rb_id2str(id) id2str(id)
159
+
160
+ // Hacked version of Ruby's rb_profile_frames from Ruby 3.0.0 with the following changes:
161
+ // 1. Instead of just using the rb_execution_context_t for the current thread, the context is received as an argument,
162
+ // thus allowing the sampling of any thread in the VM, not just the current one.
163
+ // 2. It gathers a lot more data: originally you'd get only a VALUE (either iseq or the cme, depending on the case),
164
+ // and the line number. The hacked version returns a whole raw_location with a lot more info.
165
+ // 3. It correctly ignores the dummy frame at the bottom of the main Ruby thread stack, thus mimicking the behavior of
166
+ // Ruby's backtrace_each (which is the function that is used to implement Thread#backtrace and friends)
167
+ // 4. Removed the start argument (upstream was broken anyway -- https://github.com/ruby/ruby/pull/2713 -- so we can
168
+ // re-add later if needed)
169
+ static int backtracie_rb_profile_frames_for_execution_context(
170
+ rb_execution_context_t *ec,
171
+ int limit,
172
+ raw_location *raw_locations
173
+ ) {
174
+ int i = 0;
175
+ const rb_control_frame_t *cfp = ec->cfp;
176
+ const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
177
+ const rb_callable_method_entry_t *cme = 0;
178
+
179
+ // Hack #3 above: Here we go back one frame in addition to what the original Ruby rb_profile_frames method did.
180
+ // Why? According to backtrace_each() in vm_backtrace.c there's two "dummy frames" (what MRI calls them) at the
181
+ // bottom of the stack, and we need to skip them both.
182
+ // I have no idea why the original rb_profile_frames omits this. Without this, sampling `Thread.main` always
183
+ // returned one more frame than the regular MRI APIs (which use the aforementioned backtrace_each internally).
184
+ end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
185
+
186
+ for (i = 0; i < limit && cfp != end_cfp;) {
187
+ // Initialize the raw_location, to avoid issues
188
+ raw_locations[i].is_ruby_frame = false;
189
+ raw_locations[i].should_use_iseq = false;
190
+ raw_locations[i].should_use_cfunc_name = false;
191
+ raw_locations[i].vm_method_type = 0;
192
+ raw_locations[i].line_number = 0;
193
+ raw_locations[i].iseq = Qnil;
194
+ raw_locations[i].callable_method_entry = Qnil;
195
+ raw_locations[i].cfunc_name = Qnil;
196
+
197
+ // The current object this is getting called on!
198
+ raw_locations[i].self = cfp->self;
199
+
200
+ cme = rb_vm_frame_method_entry(cfp);
201
+
202
+ if (VM_FRAME_RUBYFRAME_P(cfp)) {
203
+ raw_locations[i].is_ruby_frame = true;
204
+ raw_locations[i].iseq = (VALUE) cfp->iseq;
205
+
206
+ if (cme) {
207
+ raw_locations[i].callable_method_entry = (VALUE) cme;
208
+ raw_locations[i].vm_method_type = cme->def->type;
209
+ }
210
+
211
+ if (!(cme && cme->def->type == VM_METHOD_TYPE_ISEQ)) {
212
+ // This comes from the original rb_profile_frames logic, which would only return the iseq when the cme
213
+ // type is not VM_METHOD_TYPE_ISEQ
214
+ raw_locations[i].should_use_iseq = true;
215
+ }
216
+
217
+ raw_locations[i].line_number = calc_lineno(cfp->iseq, cfp->pc);
218
+
219
+ i++;
220
+ } else {
221
+ if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
222
+ raw_locations[i].is_ruby_frame = false;
223
+ raw_locations[i].callable_method_entry = (VALUE) cme;
224
+ raw_locations[i].vm_method_type = cme->def->type;
225
+ raw_locations[i].line_number = 0;
226
+
227
+ i++;
228
+ }
229
+ }
230
+
231
+ cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
232
+ }
233
+
234
+ return i;
235
+ }
236
+
237
+ int backtracie_rb_profile_frames(int limit, raw_location *raw_locations) {
238
+ return backtracie_rb_profile_frames_for_execution_context(GET_EC(), limit, raw_locations);
239
+ }
240
+
241
+ int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_location *raw_locations) {
242
+ // In here we're assuming that what we got is really a Thread or its subclass. This assumption NEEDS to be verified by
243
+ // the caller, otherwise I see a segfault in your future.
244
+ rb_thread_t *thread_pointer = (rb_thread_t*) DATA_PTR(thread);
245
+
246
+ if (thread_pointer->to_kill || thread_pointer->status == THREAD_KILLED) return Qnil;
247
+
248
+ return backtracie_rb_profile_frames_for_execution_context(thread_pointer->ec, limit, raw_locations);
249
+ }
250
+
251
+ VALUE backtracie_called_id(raw_location *the_location) {
252
+ if (the_location->callable_method_entry == Qnil) return Qnil;
253
+
254
+ return ID2SYM(
255
+ ((rb_callable_method_entry_t *) the_location->callable_method_entry)
256
+ ->called_id
257
+ );
258
+ }
259
+
260
+ VALUE backtracie_defined_class(raw_location *the_location) {
261
+ if (the_location->callable_method_entry == Qnil) return Qnil;
262
+
263
+ return \
264
+ ((rb_callable_method_entry_t *) the_location->callable_method_entry)
265
+ ->defined_class;
266
+ }
267
+
268
+ VALUE backtracie_rb_vm_top_self() {
269
+ return GET_VM()->top_self;
270
+ }
271
+
272
+ bool backtracie_iseq_is_block(raw_location *the_location) {
273
+ if (the_location->iseq == Qnil) return false;
274
+
275
+ return ((rb_iseq_t *) the_location->iseq)->body->type == ISEQ_TYPE_BLOCK;
276
+ }
277
+
278
+ bool backtracie_iseq_is_eval(raw_location *the_location) {
279
+ if (the_location->iseq == Qnil) return false;
280
+
281
+ return ((rb_iseq_t *) the_location->iseq)->body->type == ISEQ_TYPE_EVAL;
282
+ }
283
+
284
+ VALUE backtracie_refinement_name(raw_location *the_location) {
285
+ VALUE defined_class = backtracie_defined_class(the_location);
286
+ if (defined_class == Qnil) return Qnil;
287
+
288
+ VALUE refinement_module = rb_class_of(defined_class);
289
+ if (!FL_TEST(refinement_module, RMODULE_IS_REFINEMENT)) return Qnil;
290
+
291
+ // The below bits are inspired by Ruby's rb_mod_to_s(VALUE)
292
+ ID id_refined_class;
293
+ CONST_ID(id_refined_class, "__refined_class__");
294
+ VALUE refined_class = rb_attr_get(refinement_module, id_refined_class);
295
+ if (refined_class == Qnil) return Qnil;
296
+
297
+ VALUE result = rb_inspect(refined_class);
298
+ rb_str_concat(result, rb_str_new2("$refinement@"));
299
+ ID id_defined_at;
300
+ CONST_ID(id_defined_at, "__defined_at__");
301
+ rb_str_concat(result, rb_inspect(rb_attr_get(refinement_module, id_defined_at)));
302
+
303
+ return result;
304
+ }
305
+
306
+ // For more details on the objective of this backport, see the comments on ruby_shards.h
307
+ // This is used for Ruby < 3.0.0
308
+ #ifdef CFUNC_FRAMES_BACKPORT_NEEDED
309
+
310
+ static const rb_callable_method_entry_t *
311
+ cframe(VALUE frame)
312
+ {
313
+ if (frame == Qnil) return NULL;
314
+
315
+ if (RB_TYPE_P(frame, T_IMEMO)) {
316
+ switch (imemo_type(frame)) {
317
+ case imemo_ment:
318
+ {
319
+ const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
320
+ switch (cme->def->type) {
321
+ case VM_METHOD_TYPE_CFUNC:
322
+ return cme;
323
+ default:
324
+ return NULL;
325
+ }
326
+ }
327
+ default:
328
+ return NULL;
329
+ }
330
+ }
331
+
332
+ return NULL;
333
+ }
334
+
335
+ static const rb_iseq_t *
336
+ frame2iseq(VALUE frame)
337
+ {
338
+ if (frame == Qnil) return NULL;
339
+
340
+ if (RB_TYPE_P(frame, T_IMEMO)) {
341
+ switch (imemo_type(frame)) {
342
+ case imemo_iseq:
343
+ return (const rb_iseq_t *)frame;
344
+ case imemo_ment:
345
+ {
346
+ const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
347
+ switch (cme->def->type) {
348
+ case VM_METHOD_TYPE_ISEQ:
349
+ return cme->def->body.iseq.iseqptr;
350
+ default:
351
+ return NULL;
352
+ }
353
+ }
354
+ default:
355
+ break;
356
+ }
357
+ }
358
+ rb_bug("frame2iseq: unreachable");
359
+ }
360
+
361
+ VALUE
362
+ backported_rb_profile_frame_method_name(VALUE frame)
363
+ {
364
+ const rb_callable_method_entry_t *cme = cframe(frame);
365
+ if (cme) {
366
+ ID mid = cme->def->original_id;
367
+ return id2str(mid);
368
+ }
369
+ const rb_iseq_t *iseq = frame2iseq(frame);
370
+ return iseq ? rb_iseq_method_name(iseq) : Qnil;
371
+ }
372
+
373
+ #endif // CFUNC_FRAMES_BACKPORT_NEEDED
374
+
375
+ #ifdef CLASSPATH_BACKPORT_NEEDED
376
+ static VALUE
377
+ frame2klass(VALUE frame)
378
+ {
379
+ if (frame == Qnil) return Qnil;
380
+
381
+ if (RB_TYPE_P(frame, T_IMEMO)) {
382
+ const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
383
+
384
+ if (imemo_type(frame) == imemo_ment) {
385
+ return cme->defined_class;
386
+ }
387
+ }
388
+ return Qnil;
389
+ }
390
+
391
+ VALUE
392
+ backported_rb_profile_frame_classpath(VALUE frame)
393
+ {
394
+ VALUE klass = frame2klass(frame);
395
+
396
+ if (klass && !NIL_P(klass)) {
397
+ if (RB_TYPE_P(klass, T_ICLASS)) {
398
+ klass = RBASIC(klass)->klass;
399
+ }
400
+ else if (FL_TEST(klass, FL_SINGLETON)) {
401
+ klass = rb_ivar_get(klass, id__attached__);
402
+ if (!RB_TYPE_P(klass, T_CLASS) && !RB_TYPE_P(klass, T_MODULE))
403
+ return rb_sprintf("#<%s:%p>", rb_class2name(rb_obj_class(klass)), (void*)klass);
404
+ }
405
+ return rb_class_path(klass);
406
+ }
407
+ else {
408
+ return Qnil;
409
+ }
410
+ }
411
+
412
+ // Oddly enough, this method is on debug.h BUT NOT on the MJIT header. Since I've had
413
+ // crashes when trying to combine the MJIT header with the regular Ruby headers, let's
414
+ // just supply the declaration for this function, as otherwise the build seems to fail
415
+ // on macOS in CI.
416
+ VALUE rb_profile_frame_singleton_method_p(VALUE frame);
417
+
418
+ static VALUE
419
+ qualified_method_name(VALUE frame, VALUE method_name)
420
+ {
421
+ if (method_name != Qnil) {
422
+ VALUE classpath = backported_rb_profile_frame_classpath(frame);
423
+ VALUE singleton_p = rb_profile_frame_singleton_method_p(frame);
424
+
425
+ if (classpath != Qnil) {
426
+ return rb_sprintf("%"PRIsVALUE"%s%"PRIsVALUE,
427
+ classpath, singleton_p == Qtrue ? "." : "#", method_name);
428
+ }
429
+ else {
430
+ return method_name;
431
+ }
432
+ }
433
+ else {
434
+ return Qnil;
435
+ }
436
+ }
437
+
438
+ VALUE
439
+ backported_rb_profile_frame_qualified_method_name(VALUE frame)
440
+ {
441
+ VALUE method_name = backported_rb_profile_frame_method_name(frame);
442
+
443
+ return qualified_method_name(frame, method_name);
444
+ }
445
+ #endif // CLASSPATH_BACKPORT_NEEDED
446
+
447
+ #endif // ifndef PRE_MJIT_RUBY
448
+
449
+ // -----------------------------------------------------------------------------
450
+
451
+ #ifdef PRE_MJIT_RUBY
452
+
453
+ #include <stdbool.h>
454
+
455
+ #include "ruby.h"
456
+
457
+ #include "ruby_shards.h"
458
+
459
+ typedef struct rb_backtrace_location_struct {
460
+ enum LOCATION_TYPE {
461
+ LOCATION_TYPE_ISEQ = 1,
462
+ LOCATION_TYPE_ISEQ_CALCED,
463
+ LOCATION_TYPE_CFUNC,
464
+ } type;
465
+
466
+ union {
467
+ struct {
468
+ const VALUE iseq; // Originally const rb_iseq_t *
469
+ union {
470
+ const VALUE *pc;
471
+ int lineno;
472
+ } lineno;
473
+ } iseq;
474
+ struct {
475
+ ID mid;
476
+ struct rb_backtrace_location_struct *prev_loc;
477
+ } cfunc;
478
+ } body;
479
+ } rb_backtrace_location_t;
480
+
481
+ struct valued_frame_info {
482
+ rb_backtrace_location_t *loc;
483
+ VALUE btobj;
484
+ };
485
+
486
+ // For Ruby < 2.6, we can't rely on the MJIT header. So we need to get... crafty. This alternative to
487
+ // backtracie_rb_profile_frames_for_execution_context(...) piggybacks on the actual
488
+ // Thread::Backtrace::Locations instances returned by Ruby's Thread#backtrace_locations, and then uses
489
+ // knowledge of the VM internal layouts to get the data we need out of them.
490
+ //
491
+ // Currently, this approach gets us a lot less information than the Ruby >= 2.6 approach (e.g. currently we
492
+ // get exactly the same as Thread::Backtrace::Locations offers), so it's a crappier replacement, but it does
493
+ // get us support for older Rubies so it's better than nothing (to check the differences in behavior, check which
494
+ // tests are disabled on < 2.6).
495
+ int backtracie_profile_frames_from_ruby_locations(
496
+ VALUE ruby_locations_array,
497
+ raw_location *raw_locations
498
+ ) {
499
+ Check_Type(ruby_locations_array, T_ARRAY);
500
+ int ruby_locations_array_size = RARRAY_LEN(ruby_locations_array);
501
+
502
+ for (int i = 0; i < ruby_locations_array_size; i++) {
503
+ // FIXME: We should validate that what we get out of the array is a Thread::Backtrace::Location instance
504
+ VALUE location = rb_ary_entry(ruby_locations_array, i);
505
+
506
+ // The leap of faith -- let's get into the data from the Location instance
507
+ struct valued_frame_info *location_payload = DATA_PTR(location);
508
+
509
+ if (location_payload->loc->type == LOCATION_TYPE_ISEQ) {
510
+ // Trigger calculation of the line number; this is calculated lazily when this method is invoked and
511
+ // it turns the location type from LOCATION_TYPE_ISEQ to LOCATION_TYPE_ISEQ_CALCED.
512
+ rb_funcall(location, rb_intern("lineno"), 0);
513
+
514
+ if (location_payload->loc->type == LOCATION_TYPE_ISEQ) {
515
+ rb_raise(rb_eRuntimeError, "Internal error: Calling #lineno didn't turn a location into a LOCATION_TYPE_ISEQ_CALCED");
516
+ }
517
+ }
518
+
519
+ if (location_payload->loc->type == LOCATION_TYPE_ISEQ || location_payload->loc->type == LOCATION_TYPE_ISEQ_CALCED) {
520
+ // Ruby frame
521
+ raw_locations[i].is_ruby_frame = true;
522
+ raw_locations[i].should_use_iseq = true;
523
+ raw_locations[i].should_use_cfunc_name = false;
524
+ raw_locations[i].vm_method_type = -1;
525
+ // FIXME: Poke MRI to always get the "calced" version, see location_lineno in vm_backtrace for the context
526
+ raw_locations[i].line_number =
527
+ location_payload->loc->type == LOCATION_TYPE_ISEQ_CALCED ?
528
+ location_payload->loc->body.iseq.lineno.lineno : 0;
529
+ raw_locations[i].iseq = (VALUE) location_payload->loc->body.iseq.iseq;
530
+ raw_locations[i].callable_method_entry = Qnil;
531
+ raw_locations[i].self = Qnil;
532
+ raw_locations[i].cfunc_name = Qnil;
533
+ } else {
534
+ // cfunc frame
535
+ raw_locations[i].is_ruby_frame = false;
536
+ raw_locations[i].should_use_iseq = false;
537
+ raw_locations[i].should_use_cfunc_name = true;
538
+ raw_locations[i].vm_method_type = -1;
539
+ raw_locations[i].line_number = 0;
540
+ raw_locations[i].iseq = Qnil;
541
+ raw_locations[i].callable_method_entry = Qnil;
542
+ raw_locations[i].self = Qnil;
543
+ raw_locations[i].cfunc_name = rb_id2str(location_payload->loc->body.cfunc.mid);
544
+ }
545
+ }
546
+
547
+ return ruby_locations_array_size;
548
+ }
549
+
550
+ VALUE backtracie_called_id(raw_location *the_location) {
551
+ // Not available on PRE_MJIT_RUBY
552
+ return Qnil;
553
+ }
554
+
555
+ VALUE backtracie_defined_class(raw_location *the_location) {
556
+ // Not available on PRE_MJIT_RUBY
557
+ return Qnil;
558
+ }
559
+
560
+ VALUE backtracie_rb_vm_top_self() {
561
+ // This is only used on gem initialization so... Let's be a bit lazy :)
562
+ return rb_eval_string("TOPLEVEL_BINDING.eval('self')");
563
+ }
564
+
565
+ bool backtracie_iseq_is_block(raw_location *the_location) {
566
+ // Not available on PRE_MJIT_RUBY
567
+ return false;
568
+ }
569
+
570
+ bool backtracie_iseq_is_eval(raw_location *the_location) {
571
+ // Not available on PRE_MJIT_RUBY
572
+ return false;
573
+ }
574
+
575
+ VALUE backported_rb_profile_frame_method_name(VALUE frame) {
576
+ // Not available on PRE_MJIT_RUBY
577
+ return Qnil;
578
+ }
579
+
580
+ VALUE backtracie_refinement_name(raw_location *the_location) {
581
+ // Not available on PRE_MJIT_RUBY
582
+ return Qnil;
583
+ }
584
+
585
+ #endif // ifdef PRE_MJIT_RUBY