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