backtracie 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,930 @@
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
+ st_lookup(RCLASS_IV_TBL(klass), id__attached__, (st_data_t *)&singleton_of);
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
+ mod_to_s_anon(klass, strout);
613
+ strbuilder_append(strout, "$anonymous");
614
+ return;
615
+ }
616
+
617
+ // Non-anonymous module/class.
618
+ // something like "#{klazz.name}"
619
+ strbuilder_append_value(strout, klass_name);
620
+ }
621
+
622
+ static void minimal_location_method_qualifier(const minimal_location_t *loc,
623
+ strbuilder_t *strout) {
624
+ // First, check if it's a special object.
625
+ if (loc->method_qualifier_contents ==
626
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
627
+ if (loc->method_qualifier.self == backtracie_main_object_instance) {
628
+ strbuilder_append(strout, "Object$<main>#");
629
+ return;
630
+ }
631
+ if (loc->method_qualifier.self == rb_mRubyVMFrozenCore) {
632
+ strbuilder_append(strout, "RubyVM::FrozenCore#");
633
+ return;
634
+ }
635
+ }
636
+
637
+ // Next, check if it's a refinement.
638
+ if (loc->method_qualifier_contents ==
639
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS) {
640
+ VALUE class_of_defined_class =
641
+ rb_class_of(loc->method_qualifier.cme_defined_class);
642
+ if (RTEST(class_of_defined_class) &&
643
+ FL_TEST(class_of_defined_class, RMODULE_IS_REFINEMENT)) {
644
+ // The method being called is defined on a refinement.
645
+ mod_to_s_refinement(class_of_defined_class, strout);
646
+ strbuilder_append(strout, "#");
647
+ return;
648
+ }
649
+ }
650
+
651
+ // Next, check if the method receiver is itself a class or module.
652
+ if (loc->method_qualifier_contents ==
653
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
654
+ if (class_or_module_or_iclass(loc->method_qualifier.self)) {
655
+ // We have something like SomeModule.foo being called directly like that,
656
+ // without an instance.
657
+ mod_to_s(loc->method_qualifier.self, strout);
658
+ strbuilder_append(strout, ".");
659
+ return;
660
+ }
661
+ }
662
+
663
+ // Means the method receiver is _not_ a module/class; so, print the name of
664
+ // the class that the method is defined on.
665
+ VALUE method_target;
666
+ switch (loc->method_qualifier_contents) {
667
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
668
+ method_target = rb_class_of(loc->method_qualifier.self);
669
+ break;
670
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
671
+ method_target = loc->method_qualifier.self_class;
672
+ break;
673
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
674
+ method_target = loc->method_qualifier.cme_defined_class;
675
+ break;
676
+ }
677
+
678
+ mod_to_s(method_target, strout);
679
+ strbuilder_append(strout, "#");
680
+ }
681
+
682
+ static void minimal_location_method_name(const minimal_location_t *loc,
683
+ strbuilder_t *strout) {
684
+ if (loc->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID) {
685
+ // With a callable method entry, things are simple; just use that.
686
+ VALUE method_name = rb_id2str(loc->method_name.cme_method_id);
687
+ strbuilder_append_value(strout, method_name);
688
+ if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
689
+ strbuilder_append(strout, "{block}");
690
+ }
691
+ } else {
692
+ // With no CME, we _DO NOT_ want to use iseq->base_label if we're a block,
693
+ // because otherwise it will print something like "block in (something)". In
694
+ // fact, using the iseq->base_label is pretty much a last resort. If we
695
+ // manage to write _anything_ else in our backtrace, we won't use it.
696
+ bool did_write_anything = false;
697
+ if (loc->method_qualifier_contents ==
698
+ BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
699
+ if (RB_TYPE_P(loc->method_qualifier.self, T_CLASS)) {
700
+ // No CME, and self being a class/module, means we're executing code
701
+ // inside a class Foo; ...; end;
702
+ strbuilder_append(strout, "{class exec}");
703
+ did_write_anything = true;
704
+ }
705
+ if (RB_TYPE_P(loc->method_qualifier.self, T_MODULE)) {
706
+ strbuilder_append(strout, "{module exec}");
707
+ did_write_anything = true;
708
+ }
709
+ }
710
+ if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
711
+ strbuilder_append(strout, "{block}");
712
+ did_write_anything = true;
713
+ }
714
+ if (!did_write_anything) {
715
+ // As a fallback, use whatever is on the base_label.
716
+ // n.b. method_name_contents is guaranteed to be set to
717
+ // CONTENTS_BASE_LABEL in this branch.
718
+ strbuilder_append_value(strout, loc->method_name.base_label);
719
+ }
720
+ }
721
+ }
722
+
723
+ // This is mostly a reimplementation of pathobj_path from vm_core.h
724
+ // returns true if a path was found, and false otherwise
725
+ static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
726
+ strbuilder_t *strout) {
727
+ if (!iseq) {
728
+ return 0;
729
+ }
730
+
731
+ VALUE path_str;
732
+ #ifdef PRE_LOCATION_PATHOBJ
733
+ rb_iseq_location_t loc = iseq->body->location;
734
+ path_str = absolute ? loc.absolute_path : loc.path;
735
+ #else
736
+ VALUE pathobj = iseq->body->location.pathobj;
737
+ if (RB_TYPE_P(pathobj, T_STRING)) {
738
+ path_str = pathobj;
739
+ } else {
740
+ BACKTRACIE_ASSERT(RB_TYPE_P(pathobj, T_ARRAY));
741
+ int path_type = absolute ? PATHOBJ_REALPATH : PATHOBJ_PATH;
742
+ path_str = RARRAY_AREF(pathobj, path_type);
743
+ }
744
+ #endif
745
+
746
+ if (RTEST(path_str)) {
747
+ strbuilder_append_value(strout, path_str);
748
+ return 1;
749
+ } else {
750
+ return 0;
751
+ }
752
+ }
753
+
754
+ /**********************************************************************
755
+ vm_backtrace.c -
756
+ $Author: ko1 $
757
+ created at: Sun Jun 03 00:14:20 2012
758
+ Copyright (C) 1993-2012 Yukihiro Matsumoto
759
+ **********************************************************************/
760
+
761
+ static int calc_lineno(const rb_iseq_t *iseq, const void *pc) {
762
+ VM_ASSERT(iseq);
763
+ VM_ASSERT(iseq->body);
764
+ VM_ASSERT(iseq->body->iseq_encoded);
765
+ VM_ASSERT(iseq->body->iseq_size);
766
+ if (!pc) {
767
+ /* This can happen during VM bootup. */
768
+ VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
769
+ VM_ASSERT(!iseq->body->local_table);
770
+ VM_ASSERT(!iseq->body->local_table_size);
771
+ return 0;
772
+ } else {
773
+ ptrdiff_t n = (const VALUE *)pc - iseq->body->iseq_encoded;
774
+ VM_ASSERT(n <= iseq->body->iseq_size);
775
+ VM_ASSERT(n >= 0);
776
+ ASSUME(n >= 0);
777
+ size_t pos = n; /* no overflow */
778
+ if (LIKELY(pos)) {
779
+ /* use pos-1 because PC points next instruction at the beginning of
780
+ * instruction */
781
+ pos--;
782
+ }
783
+ return rb_iseq_line_no(iseq, pos);
784
+ }
785
+ }
786
+
787
+ // ----------------------------------------------------------------------
788
+
789
+ /**********************************************************************
790
+
791
+ vm_insnhelper.c - instruction helper functions.
792
+
793
+ $Author$
794
+
795
+ Copyright (C) 2007 Koichi Sasada
796
+
797
+ **********************************************************************/
798
+
799
+ static rb_callable_method_entry_t *copied_check_method_entry(VALUE obj,
800
+ int can_be_svar) {
801
+ if (obj == Qfalse)
802
+ return NULL;
803
+
804
+ #if VM_CHECK_MODE > 0
805
+ if (!RB_TYPE_P(obj, T_IMEMO))
806
+ rb_bug("copied_check_method_entry: unknown type: %s", rb_obj_info(obj));
807
+ #endif
808
+
809
+ switch (imemo_type(obj)) {
810
+ case imemo_ment:
811
+ return (rb_callable_method_entry_t *)obj;
812
+ case imemo_cref:
813
+ return NULL;
814
+ case imemo_svar:
815
+ if (can_be_svar) {
816
+ return copied_check_method_entry(((struct vm_svar *)obj)->cref_or_me,
817
+ FALSE);
818
+ }
819
+ default:
820
+ #if VM_CHECK_MODE > 0
821
+ rb_bug("copied_check_method_entry: svar should not be there:");
822
+ #endif
823
+ return NULL;
824
+ }
825
+ }
826
+
827
+ static const rb_callable_method_entry_t *
828
+ copied_vm_frame_method_entry(const rb_control_frame_t *cfp) {
829
+ const VALUE *ep = cfp->ep;
830
+ rb_callable_method_entry_t *me;
831
+
832
+ while (!VM_ENV_LOCAL_P(ep)) {
833
+ if ((me = copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF],
834
+ FALSE)) != NULL)
835
+ return me;
836
+ ep = VM_ENV_PREV_EP(ep);
837
+ }
838
+
839
+ return copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
840
+ }
841
+
842
+ // ----------------------------------------------------------------------
843
+
844
+ static const rb_callable_method_entry_t *
845
+ backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp) {
846
+ #ifdef PRE_MJIT_RUBY
847
+ // In < ruby 2.6, the symbol for rb_vm_frame_method_entry is hidden
848
+ return copied_vm_frame_method_entry(cfp);
849
+ #else
850
+ return rb_vm_frame_method_entry(cfp);
851
+ #endif
852
+ }
853
+
854
+ VALUE backtracie_frame_wrapper_new(size_t count) {
855
+ frame_wrapper_t *frame_data;
856
+ VALUE wrapper =
857
+ TypedData_Make_Struct(backtracie_frame_wrapper_class, frame_wrapper_t,
858
+ &backtracie_frame_wrapper_type, frame_data);
859
+ frame_data->capa = count;
860
+ frame_data->len = 0;
861
+ frame_data->frames = xcalloc(count, sizeof(raw_location));
862
+ return wrapper;
863
+ }
864
+
865
+ raw_location *backtracie_frame_wrapper_frames(VALUE wrapper) {
866
+ frame_wrapper_t *frame_data;
867
+ TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
868
+ frame_data);
869
+ return frame_data->frames;
870
+ }
871
+ int *backtracie_frame_wrapper_len(VALUE wrapper) {
872
+ frame_wrapper_t *frame_data;
873
+ TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
874
+ frame_data);
875
+ return &frame_data->len;
876
+ }
877
+
878
+ static void backtracie_frame_wrapper_mark(void *ptr) {
879
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
880
+ for (int i = 0; i < frame_data->len; i++) {
881
+ backtracie_frame_mark_movable(&frame_data->frames[i]);
882
+ }
883
+ }
884
+ static void backtracie_frame_wrapper_compact(void *ptr) {
885
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
886
+ for (int i = 0; i < frame_data->len; i++) {
887
+ backtracie_frame_compact(&frame_data->frames[i]);
888
+ }
889
+ }
890
+ static void backtracie_frame_wrapper_free(void *ptr) {
891
+ frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
892
+ xfree(frame_data->frames);
893
+ }
894
+ static size_t backtracie_frame_wrapper_memsize(const void *ptr) {
895
+ const frame_wrapper_t *frame_data = (const frame_wrapper_t *)ptr;
896
+ return sizeof(frame_wrapper_t) + sizeof(raw_location) * frame_data->capa;
897
+ }
898
+
899
+ bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
900
+ minimal_location_t *loc) {
901
+ raw_location raw_loc;
902
+ bool ret = backtracie_capture_frame_for_thread(thread, frame_index, &raw_loc);
903
+ if (!ret) {
904
+ return ret;
905
+ }
906
+ raw_location_to_minimal_location(&raw_loc, loc);
907
+
908
+ return ret;
909
+ }
910
+
911
+ size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
912
+ char *buf, size_t buflen) {
913
+
914
+ strbuilder_t builder;
915
+ strbuilder_init(&builder, buf, buflen);
916
+ minimal_location_method_qualifier(loc, &builder);
917
+ minimal_location_method_name(loc, &builder);
918
+ return builder.attempted_size;
919
+ }
920
+
921
+ size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
922
+ char *buf, size_t buflen) {
923
+
924
+ strbuilder_t builder;
925
+ strbuilder_init(&builder, buf, buflen);
926
+ if (RTEST(loc->filename)) {
927
+ strbuilder_append_value(&builder, loc->filename);
928
+ }
929
+ return builder.attempted_size;
930
+ }