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.
@@ -0,0 +1,941 @@
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
+ // backtracie_frames.c contains a number of borrowed functions from the MRI Ruby
84
+ // (usually 3.0.0) source tree:
85
+ // * A few were copy-pasted verbatim, and are dependencies for other functions
86
+ // * A few were copy-pasted and then changed so we can add features and fixes
87
+ // * A few were copy-pasted verbatim, with the objective of backporting
88
+ // their 3.0.0 behavior to earlier Ruby versions.
89
+ //
90
+ // `git blame` usually documents which functions were added for what reason.
91
+
92
+ // Note that since the RUBY_MJIT_HEADER is a very special header, meant for
93
+ // internal use only, it has a number of quirks:
94
+ //
95
+ // 1. I've seen a few segfaults when trying to call back into original Ruby
96
+ // functions. E.g. even if the API is used
97
+ // correctly, just the mere inclusion of RUBY_MJIT_HEADER causes usage to
98
+ // crash. Thus, as much as possible, it's better to define functions OUTSIDE
99
+ // this file.
100
+ //
101
+ // 2. On Windows, I've observed "multiple definition of `something...'" (such as
102
+ // `rb_vm_ep_local_ep') whenever there
103
+ // are multiple files in the codebase that include the RUBY_MJIT_HEADER.
104
+ // It looks like (some?) Windows versions of Ruby define a bunch of functions
105
+ // in the RUBY_MJIT_HEADER itself without marking them as "static" (e.g. not
106
+ // visible to the outside of the file), and thus the linker then complains
107
+ // when linking together several files which all have these non-private
108
+ // symbols. One possible hacky solution suggested by the internets is to use
109
+ // the "-Wl,-allow-multiple-definition" linker flags to ignore this problem;
110
+ // instead I've chosen to implement all usage of the RUBY_MJIT_HEADER on this
111
+ // file -- no other file in backtracie shall include RUBY_MJIT_HEADER. It's a
112
+ // simpler approach, and hopefully avoids any problems.
113
+
114
+ #include "extconf.h"
115
+
116
+ #ifndef PRE_MJIT_RUBY
117
+ #ifndef RUBY_MJIT_HEADER_INCLUDED
118
+ #define RUBY_MJIT_HEADER_INCLUDED
119
+ #include RUBY_MJIT_HEADER
120
+ #endif
121
+ #endif
122
+
123
+ #include <stdbool.h>
124
+ #include <stdint.h>
125
+ #include <stdlib.h>
126
+
127
+ #ifdef PRE_MJIT_RUBY
128
+ // The order of includes here is very important in older versions of Ruby
129
+ // clang-format off
130
+ #include <ruby.h>
131
+ #include <vm_core.h>
132
+ #include <method.h>
133
+ #include <iseq.h>
134
+ #include <regenc.h>
135
+ // clang-format on
136
+ #endif
137
+
138
+ #include "backtracie_private.h"
139
+ #include "public/backtracie.h"
140
+ #include "strbuilder.h"
141
+
142
+ #ifndef PRE_RB_ISEQ_TYPE
143
+ // This was renamed for Ruby >= 3.2
144
+ #define RB_ISEQ_TYPE rb_iseq_type
145
+ #else
146
+ #define RB_ISEQ_TYPE iseq_type
147
+ #endif
148
+
149
+ #ifdef PRE_EXECUTION_CONTEXT
150
+ // The thread and its execution context were separated on Ruby 2.5; prior to
151
+ // that, everything was part of the thread
152
+ #define rb_execution_context_t rb_thread_t
153
+ #endif
154
+
155
+ #ifdef PRE_VM_ENV_RENAMES
156
+ #define VM_ENV_LOCAL_P VM_EP_LEP_P
157
+ #define VM_ENV_PREV_EP VM_EP_PREV_EP
158
+ #define VM_ENV_DATA_INDEX_ME_CREF -1
159
+ #define VM_FRAME_RUBYFRAME_P(cfp) RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)
160
+ #endif
161
+
162
+ // This is managed in backtracie.c
163
+ extern VALUE backtracie_main_object_instance;
164
+ extern VALUE backtracie_frame_wrapper_class;
165
+ static void raw_location_to_minimal_location(const raw_location *raw_loc,
166
+ minimal_location_t *min_loc);
167
+ static void mod_to_s_anon(VALUE klass, strbuilder_t *strout);
168
+ static void mod_to_s_refinement(VALUE klass, strbuilder_t *strout);
169
+ static void mod_to_s_singleton(VALUE klass, strbuilder_t *strout);
170
+ static void mod_to_s(VALUE klass, strbuilder_t *strout);
171
+ static void minimal_location_method_qualifier(const minimal_location_t *loc,
172
+ strbuilder_t *strout);
173
+ static void minimal_location_method_name(const minimal_location_t *loc,
174
+ strbuilder_t *strout);
175
+ static bool frame_filename(const raw_location *loc, bool absolute,
176
+ strbuilder_t *strout);
177
+ static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
178
+ strbuilder_t *strout);
179
+ static int frame_label(const raw_location *loc, bool base,
180
+ strbuilder_t *strout);
181
+ static int calc_lineno(const rb_iseq_t *iseq, const void *pc);
182
+ static const rb_callable_method_entry_t *
183
+ backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp);
184
+
185
+ static void backtracie_frame_wrapper_mark(void *ptr);
186
+ static void backtracie_frame_wrapper_compact(void *ptr);
187
+ static void backtracie_frame_wrapper_free(void *ptr);
188
+ static size_t backtracie_frame_wrapper_memsize(const void *ptr);
189
+ static const rb_data_type_t backtracie_frame_wrapper_type = {
190
+ .wrap_struct_name = "backtracie_frame_wrapper",
191
+ .function = {.dmark = backtracie_frame_wrapper_mark,
192
+ .dfree = backtracie_frame_wrapper_free,
193
+ .dsize = backtracie_frame_wrapper_memsize,
194
+ #ifndef PRE_GC_MARK_MOVABLE
195
+ .dcompact = backtracie_frame_wrapper_compact,
196
+ #endif
197
+ .reserved = {0}},
198
+ .parent = NULL,
199
+ .data = NULL,
200
+ // This is safe, because our free function does not do anything which could
201
+ // yield the GVL.
202
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
203
+ };
204
+
205
+ typedef struct {
206
+ raw_location *frames;
207
+ size_t capa;
208
+ int len;
209
+ } frame_wrapper_t;
210
+
211
+ static bool object_has_special_bt_handling(VALUE obj) {
212
+ return obj == backtracie_main_object_instance || obj == rb_mRubyVMFrozenCore;
213
+ }
214
+
215
+ static bool iseq_type_is_block_or_eval(enum RB_ISEQ_TYPE type) {
216
+ return type == ISEQ_TYPE_EVAL || type == ISEQ_TYPE_BLOCK;
217
+ }
218
+
219
+ static bool iseq_type_is_eval(enum RB_ISEQ_TYPE type) {
220
+ return type == ISEQ_TYPE_EVAL;
221
+ }
222
+
223
+ static bool iseq_is_block_or_eval(const rb_iseq_t *iseq) {
224
+ if (!iseq)
225
+ return false;
226
+ return iseq_type_is_block_or_eval(iseq->body->type);
227
+ }
228
+
229
+ static bool iseq_is_eval(const rb_iseq_t *iseq) {
230
+ if (!iseq)
231
+ return false;
232
+ return iseq_type_is_eval(iseq->body->type);
233
+ }
234
+
235
+ static bool class_or_module_or_iclass(VALUE obj) {
236
+ return RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_ICLASS) ||
237
+ RB_TYPE_P(obj, T_MODULE);
238
+ }
239
+
240
+ bool backtracie_is_thread_alive(VALUE thread) {
241
+ // In here we're assuming that what we got is really a Thread or its subclass.
242
+ // This assumption NEEDS to be verified by the caller, otherwise I see a
243
+ // segfault in your future.
244
+ rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
245
+
246
+ return !(thread_pointer->to_kill || thread_pointer->status == THREAD_KILLED);
247
+ }
248
+
249
+ void backtracie_frame_mark(const raw_location *loc) {
250
+ rb_gc_mark(loc->iseq);
251
+ rb_gc_mark(loc->callable_method_entry);
252
+ rb_gc_mark(loc->self_or_self_class);
253
+ }
254
+
255
+ void backtracie_frame_mark_movable(const raw_location *loc) {
256
+ #ifdef PRE_GC_MARK_MOVABLE
257
+ backtracie_frame_mark(loc);
258
+ #else
259
+ rb_gc_mark_movable(loc->iseq);
260
+ rb_gc_mark_movable(loc->callable_method_entry);
261
+ rb_gc_mark_movable(loc->self_or_self_class);
262
+ #endif
263
+ }
264
+
265
+ void backtracie_frame_compact(raw_location *loc) {
266
+ #ifndef PRE_GC_MARK_MOVABLE
267
+ loc->iseq = rb_gc_location(loc->iseq);
268
+ loc->callable_method_entry = rb_gc_location(loc->callable_method_entry);
269
+ loc->self_or_self_class = rb_gc_location(loc->self_or_self_class);
270
+ #endif
271
+ }
272
+
273
+ static int
274
+ backtracie_frame_count_for_execution_context(rb_execution_context_t *ec) {
275
+ const rb_control_frame_t *last_cfp = ec->cfp;
276
+ // -2 because of the two dummy frames at the bottom of the stack.
277
+ const rb_control_frame_t *start_cfp = RUBY_VM_END_CONTROL_FRAME(ec) - 2;
278
+
279
+ if (ec->cfp == NULL) {
280
+ // This case means that a new thread hasn't called _any_ ruby method yet, it
281
+ // seems. Returning zero from here is the most sensible answer.
282
+ return 0;
283
+ } else if (start_cfp < last_cfp) {
284
+ return 0;
285
+ } else {
286
+ return (int)(start_cfp - last_cfp + 1);
287
+ }
288
+ }
289
+
290
+ int backtracie_frame_count_for_thread(VALUE thread) {
291
+ if (!backtracie_is_thread_alive(thread))
292
+ return 0;
293
+ rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
294
+
295
+ #ifndef PRE_EXECUTION_CONTEXT
296
+ return backtracie_frame_count_for_execution_context(thread_pointer->ec);
297
+ #else
298
+ return backtracie_frame_count_for_execution_context(thread_pointer);
299
+ #endif
300
+ }
301
+
302
+ static bool backtracie_capture_frame_for_execution_context(
303
+ rb_execution_context_t *ec, int frame_index, raw_location *loc) {
304
+ // The frame argument is zero-based with zero being "the frame closest to
305
+ // where execution is now" (I couldn't decide if this was supposed to be the
306
+ // "top" or "bottom" of the callstack; but lower frame argument --> more
307
+ // recently called function.
308
+
309
+ const rb_control_frame_t *cfp = ec->cfp + frame_index;
310
+ if (!RUBY_VM_VALID_CONTROL_FRAME_P(cfp, RUBY_VM_END_CONTROL_FRAME(ec) - 1)) {
311
+ // -1 because of the two "dummy" frames at the bottom of the stack.
312
+ // (it's -1, not -2, because RUBY_VM_VALID_CONTROL_FRAME_P checks if the
313
+ // given frame is >= the end frame, not >).,
314
+ // Means we're past the end of the stack.
315
+ BACKTRACIE_ASSERT_FAIL("called capture_frame with an invalid index");
316
+ }
317
+
318
+ const rb_callable_method_entry_t *cme = backtracie_vm_frame_method_entry(cfp);
319
+
320
+ // Work out validity, or otherwise, of this frame.
321
+ // This expression is derived from what backtrace_each in vm_backtrace.c does.
322
+ bool is_valid = (!(cfp->iseq && !cfp->pc) &&
323
+ (VM_FRAME_RUBYFRAME_P(cfp) ||
324
+ (cme && cme->def->type == VM_METHOD_TYPE_CFUNC)));
325
+ if (!is_valid) {
326
+ // Don't include this frame in backtraces
327
+ return false;
328
+ }
329
+
330
+ loc->is_ruby_frame = VM_FRAME_RUBYFRAME_P(cfp);
331
+ loc->iseq = (VALUE)cfp->iseq;
332
+ loc->callable_method_entry = (VALUE)cme;
333
+ if (object_has_special_bt_handling(cfp->self) ||
334
+ class_or_module_or_iclass(cfp->self)) {
335
+ loc->self_or_self_class = cfp->self;
336
+ loc->self_is_real_self = 1;
337
+ } else {
338
+ loc->self_or_self_class = rb_class_of(cfp->self);
339
+ loc->self_is_real_self = 0;
340
+ }
341
+ loc->pc = cfp->pc;
342
+ return true;
343
+ }
344
+
345
+ bool backtracie_capture_frame_for_thread(VALUE thread, int frame_index,
346
+ raw_location *loc) {
347
+ if (!backtracie_is_thread_alive(thread)) {
348
+ return false;
349
+ }
350
+ rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
351
+
352
+ #ifndef PRE_EXECUTION_CONTEXT
353
+ return backtracie_capture_frame_for_execution_context(thread_pointer->ec,
354
+ frame_index, loc);
355
+ #else
356
+ return backtracie_capture_frame_for_execution_context(thread_pointer,
357
+ frame_index, loc);
358
+ #endif
359
+ }
360
+
361
+ int backtracie_frame_line_number(const raw_location *loc) {
362
+ return calc_lineno((rb_iseq_t *)loc->iseq, loc->pc);
363
+ }
364
+
365
+ size_t backtracie_frame_name_cstr(const raw_location *loc, char *buf,
366
+ size_t buflen) {
367
+ strbuilder_t builder;
368
+ strbuilder_init(&builder, buf, buflen);
369
+
370
+ minimal_location_t min_loc;
371
+ raw_location_to_minimal_location(loc, &min_loc);
372
+ minimal_location_method_qualifier(&min_loc, &builder);
373
+ minimal_location_method_name(&min_loc, &builder);
374
+
375
+ return builder.attempted_size;
376
+ }
377
+
378
+ VALUE backtracie_frame_name_rbstr(const raw_location *loc) {
379
+ strbuilder_t builder;
380
+ strbuilder_init_growable(&builder, 256);
381
+
382
+ minimal_location_t min_loc;
383
+ raw_location_to_minimal_location(loc, &min_loc);
384
+ minimal_location_method_qualifier(&min_loc, &builder);
385
+ minimal_location_method_name(&min_loc, &builder);
386
+
387
+ VALUE ret = strbuilder_to_value(&builder);
388
+ strbuilder_free_growable(&builder);
389
+ return ret;
390
+ }
391
+
392
+ size_t backtracie_frame_filename_cstr(const raw_location *loc, bool absolute,
393
+ char *buf, size_t buflen) {
394
+ strbuilder_t builder;
395
+ strbuilder_init(&builder, buf, buflen);
396
+
397
+ frame_filename(loc, absolute, &builder);
398
+
399
+ return builder.attempted_size;
400
+ }
401
+
402
+ VALUE backtracie_frame_filename_rbstr(const raw_location *loc, bool absolute) {
403
+ strbuilder_t builder;
404
+ strbuilder_init_growable(&builder, 256);
405
+
406
+ bool fname_found = frame_filename(loc, absolute, &builder);
407
+
408
+ VALUE ret = Qnil;
409
+ if (fname_found) {
410
+ ret = strbuilder_to_value(&builder);
411
+ }
412
+ strbuilder_free_growable(&builder);
413
+ return ret;
414
+ }
415
+
416
+ static bool frame_filename(const raw_location *loc, bool absolute,
417
+ strbuilder_t *strout) {
418
+ return iseq_path((const rb_iseq_t *)loc->iseq, absolute, strout);
419
+ }
420
+
421
+ size_t backtracie_frame_label_cstr(const raw_location *loc, bool base,
422
+ char *buf, size_t buflen) {
423
+ strbuilder_t builder;
424
+ strbuilder_init(&builder, buf, buflen);
425
+
426
+ frame_label(loc, base, &builder);
427
+
428
+ return builder.attempted_size;
429
+ }
430
+
431
+ VALUE backtracie_frame_label_rbstr(const raw_location *loc, bool base) {
432
+ strbuilder_t builder;
433
+ strbuilder_init_growable(&builder, 256);
434
+
435
+ int label_found = frame_label(loc, base, &builder);
436
+
437
+ VALUE ret = Qnil;
438
+ if (label_found) {
439
+ ret = strbuilder_to_value(&builder);
440
+ }
441
+ strbuilder_free_growable(&builder);
442
+ return ret;
443
+ }
444
+
445
+ VALUE backtracie_frame_for_rb_profile(const raw_location *loc) {
446
+ const rb_iseq_t *iseq = NULL;
447
+ const rb_callable_method_entry_t *cme = NULL;
448
+ if (RTEST(loc->iseq)) {
449
+ iseq = (rb_iseq_t *)loc->iseq;
450
+ }
451
+ if (RTEST(loc->callable_method_entry)) {
452
+ cme = (rb_callable_method_entry_t *)cme;
453
+ }
454
+
455
+ // This one is somewhat weird, but the regular MRI Ruby APIs seem to pick the
456
+ // iseq for evals as well
457
+ if (iseq_is_eval(iseq)) {
458
+ return loc->iseq;
459
+ }
460
+ // This comes from the original rb_profile_frames logic, which would only
461
+ // return the iseq when the cme type is not VM_METHOD_TYPE_ISEQ
462
+ if (cme && cme->def->type != VM_METHOD_TYPE_ISEQ) {
463
+ return loc->callable_method_entry;
464
+ }
465
+
466
+ if (iseq) {
467
+ return loc->iseq;
468
+ }
469
+ return Qnil;
470
+ }
471
+
472
+ static int frame_label(const raw_location *loc, bool base,
473
+ strbuilder_t *strout) {
474
+ if (loc->is_ruby_frame) {
475
+ // Replicate what rb_profile_frames would do
476
+ if (!RTEST(loc->iseq)) {
477
+ return 0;
478
+ }
479
+ rb_iseq_t *iseq = (rb_iseq_t *)loc->iseq;
480
+ VALUE label =
481
+ base ? iseq->body->location.base_label : iseq->body->location.label;
482
+ strbuilder_append_value(strout, label);
483
+ } else {
484
+ if (!RTEST(loc->callable_method_entry)) {
485
+ return 0;
486
+ }
487
+ rb_callable_method_entry_t *cme =
488
+ (rb_callable_method_entry_t *)loc->callable_method_entry;
489
+ strbuilder_append_value(strout, rb_id2str(cme->def->original_id));
490
+ }
491
+ return 1;
492
+ }
493
+
494
+ static void raw_location_to_minimal_location(const raw_location *raw_loc,
495
+ minimal_location_t *min_loc) {
496
+
497
+ min_loc->is_ruby_frame = raw_loc->is_ruby_frame;
498
+ if (RTEST(raw_loc->callable_method_entry)) {
499
+ min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID;
500
+ min_loc->method_name.cme_method_id =
501
+ ((rb_callable_method_entry_t *)raw_loc->callable_method_entry)
502
+ ->called_id;
503
+ } else if (RTEST(raw_loc->iseq)) {
504
+ min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL;
505
+ min_loc->method_name.base_label =
506
+ ((rb_iseq_t *)raw_loc->iseq)->body->location.base_label;
507
+ } else {
508
+ min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL;
509
+ min_loc->method_name.base_label = Qnil;
510
+ }
511
+
512
+ if (RTEST(raw_loc->iseq)) {
513
+ min_loc->has_iseq_type = 1;
514
+ min_loc->iseq_type = ((rb_iseq_t *)raw_loc->iseq)->body->type;
515
+ min_loc->line_number = calc_lineno((rb_iseq_t *)raw_loc->iseq, raw_loc->pc);
516
+ } else {
517
+ min_loc->has_iseq_type = 0;
518
+ min_loc->line_number = 0;
519
+ }
520
+
521
+ if (RTEST(raw_loc->self_is_real_self)) {
522
+ min_loc->method_qualifier_contents =
523
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF;
524
+ min_loc->method_qualifier.self = raw_loc->self_or_self_class;
525
+ } else if (RTEST(raw_loc->callable_method_entry)) {
526
+ min_loc->method_qualifier_contents =
527
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS;
528
+ min_loc->method_qualifier.cme_defined_class =
529
+ ((rb_callable_method_entry_t *)raw_loc->callable_method_entry)
530
+ ->defined_class;
531
+ } else {
532
+ min_loc->method_qualifier_contents =
533
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS;
534
+ min_loc->method_qualifier.self_class = raw_loc->self_or_self_class;
535
+ }
536
+ }
537
+
538
+ static void mod_to_s_anon(VALUE klass, strbuilder_t *strout) {
539
+ // Anonymous module/class - print the name of the first non-anonymous super.
540
+ // something like "#{klazz.ancestors.map(&:name).compact.first}$anonymous"
541
+ //
542
+ // Note that if klazz is a module, we want to do this on klazz.class, not
543
+ // klazz itself:
544
+ //
545
+ // irb(main):008:0> m = Module.new
546
+ // => #<Module:0x00000000021a7208>
547
+ // irb(main):009:0> m.ancestors
548
+ // => [#<Module:0x00000000021a7208>]
549
+ // # Not very useful - nothing with a name is in the ancestor chain
550
+ // irb(main):010:0> m.class.ancestors
551
+ // => [Module, Object, Kernel, BasicObject]
552
+ // # Much more useful - we can call this Module$anonymous.
553
+ //
554
+ VALUE superclass = klass;
555
+ VALUE superclass_name = Qnil;
556
+ // Find an actual class - every _class_ is guaranteed to be a descendant of
557
+ // BasicObject at least, which has a name, so we'll be able to name this
558
+ // _something_.
559
+ while (!RB_TYPE_P(superclass, T_CLASS)) {
560
+ superclass = rb_class_of(superclass);
561
+ }
562
+ do {
563
+ superclass = rb_class_superclass(superclass);
564
+ BACKTRACIE_ASSERT(RTEST(superclass));
565
+ superclass_name = rb_mod_name(superclass);
566
+ } while (!RTEST(superclass_name));
567
+ strbuilder_append_value(strout, superclass_name);
568
+ }
569
+
570
+ static void mod_to_s_singleton(VALUE klass, strbuilder_t *strout) {
571
+ VALUE singleton_of = rb_class_real(klass);
572
+ // If this is the singleton_class of a Class, or Module, we want to print
573
+ // the _value_ of the object, and _NOT_ its class.
574
+ // Basically:
575
+ // module MyModule; end;
576
+ // klazz = MyModule.singleton_class =>
577
+ // we want to output "MyModule"
578
+ //
579
+ // klazz = Something.new.singleton_class =>
580
+ // we want to output "Something"
581
+ //
582
+ if (singleton_of == rb_cModule || singleton_of == rb_cClass) {
583
+ // The first case. Use the id_attached symbol to get what this is the
584
+ // singleton_class _of_.
585
+ singleton_of = rb_ivar_get(klass, id__attached__);
586
+ }
587
+ mod_to_s(singleton_of, strout);
588
+ }
589
+
590
+ static void mod_to_s_refinement(VALUE refinement_module, strbuilder_t *strout) {
591
+ ID id_refined_class;
592
+ CONST_ID(id_refined_class, "__refined_class__");
593
+ VALUE refined_class = rb_attr_get(refinement_module, id_refined_class);
594
+ ID id_defined_at;
595
+ CONST_ID(id_defined_at, "__defined_at__");
596
+ VALUE defined_at = rb_attr_get(refinement_module, id_defined_at);
597
+
598
+ mod_to_s(refined_class, strout);
599
+ strbuilder_append(strout, "$refinement@");
600
+ mod_to_s(defined_at, strout);
601
+ }
602
+
603
+ static void mod_to_s(VALUE klass, strbuilder_t *strout) {
604
+ if (FL_TEST(klass, FL_SINGLETON)) {
605
+ mod_to_s_singleton(klass, strout);
606
+ strbuilder_append(strout, "$singleton");
607
+ return;
608
+ }
609
+
610
+ VALUE klass_name = rb_mod_name(klass);
611
+ if (!RTEST(rb_mod_name(klass))) {
612
+ // If I understood it correctly, a T_ICLASS represents the inclusion of a module into
613
+ // a class. In this situation, we usually want to use the module name instead.
614
+ if (RB_TYPE_P(klass, T_ICLASS)) {
615
+ VALUE included_module = RBASIC(klass)->klass;
616
+ mod_to_s(included_module, strout);
617
+ return;
618
+ } else {
619
+ mod_to_s_anon(klass, strout);
620
+ strbuilder_append(strout, "$anonymous");
621
+ return;
622
+ }
623
+ }
624
+
625
+ // Non-anonymous module/class.
626
+ // something like "#{klazz.name}"
627
+ strbuilder_append_value(strout, klass_name);
628
+ }
629
+
630
+ static void minimal_location_method_qualifier(const minimal_location_t *loc,
631
+ strbuilder_t *strout) {
632
+ // First, check if it's a special object.
633
+ if (loc->method_qualifier_contents ==
634
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
635
+ if (loc->method_qualifier.self == backtracie_main_object_instance) {
636
+ strbuilder_append(strout, "Object$<main>#");
637
+ return;
638
+ }
639
+ if (loc->method_qualifier.self == rb_mRubyVMFrozenCore) {
640
+ strbuilder_append(strout, "RubyVM::FrozenCore#");
641
+ return;
642
+ }
643
+ }
644
+
645
+ // Next, check if it's a refinement.
646
+ if (loc->method_qualifier_contents ==
647
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS) {
648
+ VALUE class_of_defined_class =
649
+ rb_class_of(loc->method_qualifier.cme_defined_class);
650
+ if (RTEST(class_of_defined_class) &&
651
+ FL_TEST(class_of_defined_class, RMODULE_IS_REFINEMENT)) {
652
+ // The method being called is defined on a refinement.
653
+ mod_to_s_refinement(class_of_defined_class, strout);
654
+ strbuilder_append(strout, "#");
655
+ return;
656
+ }
657
+ }
658
+
659
+ // Next, check if the method receiver is itself a class or module.
660
+ if (loc->method_qualifier_contents ==
661
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
662
+ if (class_or_module_or_iclass(loc->method_qualifier.self)) {
663
+ // We have something like SomeModule.foo being called directly like that,
664
+ // without an instance.
665
+ mod_to_s(loc->method_qualifier.self, strout);
666
+ strbuilder_append(strout, ".");
667
+ return;
668
+ }
669
+ }
670
+
671
+ // Means the method receiver is _not_ a module/class; so, print the name of
672
+ // the class that the method is defined on.
673
+ VALUE method_target;
674
+ switch (loc->method_qualifier_contents) {
675
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
676
+ method_target = rb_class_of(loc->method_qualifier.self);
677
+ break;
678
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
679
+ method_target = loc->method_qualifier.self_class;
680
+ break;
681
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
682
+ method_target = loc->method_qualifier.cme_defined_class;
683
+ break;
684
+ default:
685
+ strbuilder_append(strout, "FIXME_SHOULD_NEVER_HAPPEN");
686
+ return;
687
+ }
688
+
689
+ mod_to_s(method_target, strout);
690
+ strbuilder_append(strout, "#");
691
+ }
692
+
693
+ static void minimal_location_method_name(const minimal_location_t *loc,
694
+ strbuilder_t *strout) {
695
+ if (loc->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID) {
696
+ // With a callable method entry, things are simple; just use that.
697
+ VALUE method_name = rb_id2str(loc->method_name.cme_method_id);
698
+ strbuilder_append_value(strout, method_name);
699
+ if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
700
+ strbuilder_append(strout, "{block}");
701
+ }
702
+ } else {
703
+ // With no CME, we _DO NOT_ want to use iseq->base_label if we're a block,
704
+ // because otherwise it will print something like "block in (something)". In
705
+ // fact, using the iseq->base_label is pretty much a last resort. If we
706
+ // manage to write _anything_ else in our backtrace, we won't use it.
707
+ bool did_write_anything = false;
708
+ if (loc->method_qualifier_contents ==
709
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
710
+ if (RB_TYPE_P(loc->method_qualifier.self, T_CLASS)) {
711
+ // No CME, and self being a class/module, means we're executing code
712
+ // inside a class Foo; ...; end;
713
+ strbuilder_append(strout, "{class exec}");
714
+ did_write_anything = true;
715
+ }
716
+ if (RB_TYPE_P(loc->method_qualifier.self, T_MODULE)) {
717
+ strbuilder_append(strout, "{module exec}");
718
+ did_write_anything = true;
719
+ }
720
+ }
721
+ if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
722
+ strbuilder_append(strout, "{block}");
723
+ did_write_anything = true;
724
+ }
725
+ if (!did_write_anything) {
726
+ // As a fallback, use whatever is on the base_label.
727
+ // n.b. method_name_contents is guaranteed to be set to
728
+ // CONTENTS_BASE_LABEL in this branch.
729
+ strbuilder_append_value(strout, loc->method_name.base_label);
730
+ }
731
+ }
732
+ }
733
+
734
+ // This is mostly a reimplementation of pathobj_path from vm_core.h
735
+ // returns true if a path was found, and false otherwise
736
+ static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
737
+ strbuilder_t *strout) {
738
+ if (!iseq) {
739
+ return 0;
740
+ }
741
+
742
+ VALUE path_str;
743
+ #ifdef PRE_LOCATION_PATHOBJ
744
+ rb_iseq_location_t loc = iseq->body->location;
745
+ path_str = absolute ? loc.absolute_path : loc.path;
746
+ #else
747
+ VALUE pathobj = iseq->body->location.pathobj;
748
+ if (RB_TYPE_P(pathobj, T_STRING)) {
749
+ path_str = pathobj;
750
+ } else {
751
+ BACKTRACIE_ASSERT(RB_TYPE_P(pathobj, T_ARRAY));
752
+ int path_type = absolute ? PATHOBJ_REALPATH : PATHOBJ_PATH;
753
+ path_str = RARRAY_AREF(pathobj, path_type);
754
+ }
755
+ #endif
756
+
757
+ if (RTEST(path_str)) {
758
+ strbuilder_append_value(strout, path_str);
759
+ return 1;
760
+ } else {
761
+ return 0;
762
+ }
763
+ }
764
+
765
+ /**********************************************************************
766
+ vm_backtrace.c -
767
+ $Author: ko1 $
768
+ created at: Sun Jun 03 00:14:20 2012
769
+ Copyright (C) 1993-2012 Yukihiro Matsumoto
770
+ **********************************************************************/
771
+
772
+ static int calc_lineno(const rb_iseq_t *iseq, const void *pc) {
773
+ VM_ASSERT(iseq);
774
+ VM_ASSERT(iseq->body);
775
+ VM_ASSERT(iseq->body->iseq_encoded);
776
+ VM_ASSERT(iseq->body->iseq_size);
777
+ if (!pc) {
778
+ /* This can happen during VM bootup. */
779
+ VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
780
+ VM_ASSERT(!iseq->body->local_table);
781
+ VM_ASSERT(!iseq->body->local_table_size);
782
+ return 0;
783
+ } else {
784
+ ptrdiff_t n = (const VALUE *)pc - iseq->body->iseq_encoded;
785
+ VM_ASSERT(n <= iseq->body->iseq_size);
786
+ VM_ASSERT(n >= 0);
787
+ ASSUME(n >= 0);
788
+ size_t pos = n; /* no overflow */
789
+ if (LIKELY(pos)) {
790
+ /* use pos-1 because PC points next instruction at the beginning of
791
+ * instruction */
792
+ pos--;
793
+ }
794
+ return rb_iseq_line_no(iseq, pos);
795
+ }
796
+ }
797
+
798
+ // ----------------------------------------------------------------------
799
+
800
+ /**********************************************************************
801
+
802
+ vm_insnhelper.c - instruction helper functions.
803
+
804
+ $Author$
805
+
806
+ Copyright (C) 2007 Koichi Sasada
807
+
808
+ **********************************************************************/
809
+
810
+ static rb_callable_method_entry_t *copied_check_method_entry(VALUE obj,
811
+ int can_be_svar) {
812
+ if (obj == Qfalse)
813
+ return NULL;
814
+
815
+ #if VM_CHECK_MODE > 0
816
+ if (!RB_TYPE_P(obj, T_IMEMO))
817
+ rb_bug("copied_check_method_entry: unknown type: %s", rb_obj_info(obj));
818
+ #endif
819
+
820
+ switch (imemo_type(obj)) {
821
+ case imemo_ment:
822
+ return (rb_callable_method_entry_t *)obj;
823
+ case imemo_cref:
824
+ return NULL;
825
+ case imemo_svar:
826
+ if (can_be_svar) {
827
+ return copied_check_method_entry(((struct vm_svar *)obj)->cref_or_me,
828
+ FALSE);
829
+ }
830
+ default:
831
+ #if VM_CHECK_MODE > 0
832
+ rb_bug("copied_check_method_entry: svar should not be there:");
833
+ #endif
834
+ return NULL;
835
+ }
836
+ }
837
+
838
+ static const rb_callable_method_entry_t *
839
+ copied_vm_frame_method_entry(const rb_control_frame_t *cfp) {
840
+ const VALUE *ep = cfp->ep;
841
+ rb_callable_method_entry_t *me;
842
+
843
+ while (!VM_ENV_LOCAL_P(ep)) {
844
+ if ((me = copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF],
845
+ FALSE)) != NULL)
846
+ return me;
847
+ ep = VM_ENV_PREV_EP(ep);
848
+ }
849
+
850
+ return copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
851
+ }
852
+
853
+ // ----------------------------------------------------------------------
854
+
855
+ static const rb_callable_method_entry_t *
856
+ backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp) {
857
+ #ifdef PRE_MJIT_RUBY
858
+ // In < ruby 2.6, the symbol for rb_vm_frame_method_entry is hidden
859
+ return copied_vm_frame_method_entry(cfp);
860
+ #else
861
+ return rb_vm_frame_method_entry(cfp);
862
+ #endif
863
+ }
864
+
865
+ VALUE backtracie_frame_wrapper_new(size_t count) {
866
+ frame_wrapper_t *frame_data;
867
+ VALUE wrapper =
868
+ TypedData_Make_Struct(backtracie_frame_wrapper_class, frame_wrapper_t,
869
+ &backtracie_frame_wrapper_type, frame_data);
870
+ frame_data->capa = count;
871
+ frame_data->len = 0;
872
+ frame_data->frames = xcalloc(count, sizeof(raw_location));
873
+ return wrapper;
874
+ }
875
+
876
+ raw_location *backtracie_frame_wrapper_frames(VALUE wrapper) {
877
+ frame_wrapper_t *frame_data;
878
+ TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
879
+ frame_data);
880
+ return frame_data->frames;
881
+ }
882
+ int *backtracie_frame_wrapper_len(VALUE wrapper) {
883
+ frame_wrapper_t *frame_data;
884
+ TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
885
+ frame_data);
886
+ return &frame_data->len;
887
+ }
888
+
889
+ static void backtracie_frame_wrapper_mark(void *ptr) {
890
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
891
+ for (int i = 0; i < frame_data->len; i++) {
892
+ backtracie_frame_mark_movable(&frame_data->frames[i]);
893
+ }
894
+ }
895
+ static void backtracie_frame_wrapper_compact(void *ptr) {
896
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
897
+ for (int i = 0; i < frame_data->len; i++) {
898
+ backtracie_frame_compact(&frame_data->frames[i]);
899
+ }
900
+ }
901
+ static void backtracie_frame_wrapper_free(void *ptr) {
902
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
903
+ xfree(frame_data->frames);
904
+ }
905
+ static size_t backtracie_frame_wrapper_memsize(const void *ptr) {
906
+ const frame_wrapper_t *frame_data = (const frame_wrapper_t *)ptr;
907
+ return sizeof(frame_wrapper_t) + sizeof(raw_location) * frame_data->capa;
908
+ }
909
+
910
+ bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
911
+ minimal_location_t *loc) {
912
+ raw_location raw_loc;
913
+ bool ret = backtracie_capture_frame_for_thread(thread, frame_index, &raw_loc);
914
+ if (!ret) {
915
+ return ret;
916
+ }
917
+ raw_location_to_minimal_location(&raw_loc, loc);
918
+
919
+ return ret;
920
+ }
921
+
922
+ size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
923
+ char *buf, size_t buflen) {
924
+
925
+ strbuilder_t builder;
926
+ strbuilder_init(&builder, buf, buflen);
927
+ minimal_location_method_qualifier(loc, &builder);
928
+ minimal_location_method_name(loc, &builder);
929
+ return builder.attempted_size;
930
+ }
931
+
932
+ size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
933
+ char *buf, size_t buflen) {
934
+
935
+ strbuilder_t builder;
936
+ strbuilder_init(&builder, buf, buflen);
937
+ if (RTEST(loc->filename)) {
938
+ strbuilder_append_value(&builder, loc->filename);
939
+ }
940
+ return builder.attempted_size;
941
+ }