backtracie 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69190b4b5e00e3f8d89b96288a795bf79aee1f310d4e40d81d05a4737d99f9eb
4
- data.tar.gz: d14cf978670bdd754a257c6b58735fb19921da15e2cd6a7a4c07ed0a8ddd6856
3
+ metadata.gz: b8ef71cf1cfd9ca005e0ecb0fe7434a9c8121bc46fc70c34209c74ecad1c705c
4
+ data.tar.gz: 723d3c66fb919432481428609a8316243e160805ff4142757f9ec65cf254f2d8
5
5
  SHA512:
6
- metadata.gz: 5341c80c6ec5a4aeb9c6e0ad3e8f95126aa1f7502f4665b3a00be55349fd8312b5c3e2601162f5ec7c2ae458229fc38fa668c4919523e781fda5f04a86f01044
7
- data.tar.gz: da4b1a46513861bdb05fe0e6d919d0350a1de47b0678b505cb494330677998a68c6c8188f8dfbd5cbd206aad6193c89bb7306f7447ac375c7536387dc3159cab
6
+ metadata.gz: ceffbd048f44b6563298f301bde8dd498c1ea92be244e90a42390f4d387d58ef3837d936afbd4c63376ebfa2a20984989a2f8e1426ab9cccc30fc8c844c78a68
7
+ data.tar.gz: cc6e34b726934f01ae92bf089954bace7063619ce8e3d71b1d1eea610d153f10641257399388ef70bcd7730b1aea89039d4f7e6e2b960f2b09e937fd4d802969
data/README.adoc CHANGED
@@ -8,9 +8,10 @@ A Ruby gem for beautiful backtraces.
8
8
 
9
9
  Aims to offer alternatives to `Thread#backtrace`, `Thread#backtrace_locations`, `Kernel#caller` and `Kernel#caller_locations` with similar (or better!?) performance, but with better metadata (such as class names).
10
10
 
11
- WARNING: **Currently this gem is an exploration ground for some... questionable low-level techniques.**
12
- **Due to the way it works, bugs can easily cause your whole Ruby VM to crash.**
13
- **So please take this into account if you want to use this gem for anything. But contributions and bug reports are mega welcome.**
11
+ WARNING: **⚠️ This gem is currently an exploration ground for the use of low-level techniques to get at internal Ruby VM structures.
12
+ Due to the way it works, bugs can easily cause your whole Ruby VM to crash;
13
+ Please take this into account if you plan on using this gem.
14
+ Still, contributions and bug reports are mega welcome, and I hope to one day be able to remove this warning ⚠️.**
14
15
 
15
16
  image:https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg["Contributor Covenant", link="CODE_OF_CONDUCT.adoc"]
16
17
  image:https://badge.fury.io/rb/backtracie.svg["Gem Version", link="https://badge.fury.io/rb/backtracie"]
data/backtracie.gemspec CHANGED
@@ -46,4 +46,7 @@ Gem::Specification.new do |spec|
46
46
  end
47
47
  spec.require_paths = ["lib", "ext"]
48
48
  spec.extensions = ["ext/backtracie_native_extension/extconf.rb"]
49
+
50
+ # Enables support for Ruby 2.5 and below. Not used at all in the others.
51
+ spec.add_dependency "debase-ruby_core_source", "~> 0.10", ">= 0.10.12"
49
52
  end
@@ -33,8 +33,6 @@
33
33
  static VALUE main_object_instance = Qnil;
34
34
  static ID ensure_object_is_thread_id;
35
35
  static ID to_s_id;
36
- static ID current_id;
37
- static ID backtrace_locations_id;
38
36
  static VALUE backtracie_module = Qnil;
39
37
  static VALUE backtracie_location_class = Qnil;
40
38
 
@@ -52,14 +50,12 @@ static bool is_self_class_singleton(raw_location *the_location);
52
50
  static bool is_defined_class_a_refinement(raw_location *the_location);
53
51
  static VALUE debug_raw_location(raw_location *the_location);
54
52
  static VALUE debug_frame(VALUE frame);
55
- static inline VALUE to_boolean(bool value) ;
53
+ static inline VALUE to_boolean(bool value);
56
54
 
57
55
  void Init_backtracie_native_extension(void) {
58
- main_object_instance = backtracie_rb_vm_top_self();
56
+ main_object_instance = rb_funcall(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), rb_intern("eval"), 1, rb_str_new2("self"));
59
57
  ensure_object_is_thread_id = rb_intern("ensure_object_is_thread");
60
58
  to_s_id = rb_intern("to_s");
61
- current_id = rb_intern("current");
62
- backtrace_locations_id = rb_intern("backtrace_locations");
63
59
 
64
60
  backtracie_module = rb_const_get(rb_cObject, rb_intern("Backtracie"));
65
61
  rb_global_variable(&backtracie_module);
@@ -79,28 +75,13 @@ static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_s
79
75
  int stack_depth = 0;
80
76
  raw_location raw_locations[MAX_STACK_DEPTH];
81
77
 
82
- #ifndef PRE_MJIT_RUBY
83
- if (thread == Qnil) {
84
- // Get for own thread
85
- stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
86
- } else {
87
- stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
88
- if (stack_depth == 0 && !backtracie_is_thread_alive(thread)) return Qnil;
89
- }
90
- #else
91
- VALUE current_thread = rb_funcall(rb_cThread, current_id, 0);
92
-
93
- if (thread == Qnil || thread == current_thread) {
94
- thread = current_thread;
95
- // Since we're going to sample the current thread, we don't want Thread#backtrace_locations to show up
96
- // on the stack (as we want to match the exact depth we get from MRI), so we ignore one extra frame
97
- ignored_stack_top_frames++;
98
- }
99
-
100
- VALUE locations_array = rb_funcall(thread, backtrace_locations_id, 0);
101
- if (locations_array == Qnil) return Qnil;
102
- stack_depth = backtracie_profile_frames_from_ruby_locations(locations_array, raw_locations);
103
- #endif
78
+ if (thread == Qnil) {
79
+ // Get for own thread
80
+ stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
81
+ } else {
82
+ stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
83
+ if (stack_depth == 0 && !backtracie_is_thread_alive(thread)) return Qnil;
84
+ }
104
85
 
105
86
  VALUE locations = rb_ary_new_capa(stack_depth - ignored_stack_top_frames);
106
87
 
@@ -217,9 +198,7 @@ static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *l
217
198
  last_ruby_location != 0 ? frame_from_location(last_ruby_location) : Qnil;
218
199
 
219
200
  // Replaces label and base_label in cfuncs
220
- VALUE method_name =
221
- the_location->should_use_cfunc_name ?
222
- the_location->cfunc_name : backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
201
+ VALUE method_name = backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
223
202
 
224
203
  return new_location(
225
204
  SAFE_NAVIGATION(rb_profile_frame_absolute_path, last_ruby_frame),
@@ -317,7 +296,6 @@ static VALUE debug_raw_location(raw_location *the_location) {
317
296
  VALUE arguments[] = {
318
297
  ID2SYM(rb_intern("ruby_frame?")) , /* => */ to_boolean(the_location->is_ruby_frame),
319
298
  ID2SYM(rb_intern("should_use_iseq")), /* => */ to_boolean(the_location->should_use_iseq),
320
- ID2SYM(rb_intern("should_use_cfunc_name")), /* => */ to_boolean(the_location->should_use_cfunc_name),
321
299
  ID2SYM(rb_intern("vm_method_type")), /* => */ INT2FIX(the_location->vm_method_type),
322
300
  ID2SYM(rb_intern("line_number")), /* => */ INT2FIX(the_location->line_number),
323
301
  ID2SYM(rb_intern("called_id")), /* => */ backtracie_called_id(the_location),
@@ -331,16 +309,13 @@ static VALUE debug_raw_location(raw_location *the_location) {
331
309
  ID2SYM(rb_intern("self_class_singleton?")), /* => */ to_boolean(is_self_class_singleton(the_location)),
332
310
  ID2SYM(rb_intern("iseq_is_block?")), /* => */ to_boolean(backtracie_iseq_is_block(the_location)),
333
311
  ID2SYM(rb_intern("iseq_is_eval?")), /* => */ to_boolean(backtracie_iseq_is_eval(the_location)),
334
- ID2SYM(rb_intern("cfunc_name")), /* => */ the_location->cfunc_name,
335
312
  ID2SYM(rb_intern("iseq")), /* => */ debug_frame(the_location->iseq),
336
- ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry)
313
+ ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry),
314
+ ID2SYM(rb_intern("original_id")), /* => */ the_location->original_id
337
315
  };
338
316
 
339
317
  VALUE debug_hash = rb_hash_new();
340
- #ifndef PRE_MJIT_RUBY
341
- // FIXME: Hack, need to actually fix this
342
- rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
343
- #endif
318
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
344
319
  return debug_hash;
345
320
  }
346
321
 
@@ -361,10 +336,7 @@ static VALUE debug_frame(VALUE frame) {
361
336
  };
362
337
 
363
338
  VALUE debug_hash = rb_hash_new();
364
- #ifndef PRE_MJIT_RUBY
365
- // FIXME: Hack, need to actually fix this
366
- rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
367
- #endif
339
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
368
340
  return debug_hash;
369
341
  }
370
342
 
@@ -34,6 +34,10 @@ $CFLAGS << " " << "-Wno-unused-function"
34
34
  # Really dislike the "define everything at the beginning of the function" thing, sorry!
35
35
  $CFLAGS << " " << "-Wno-declaration-after-statement"
36
36
 
37
+ # If we forget to include a Ruby header, the function call may still appear to work, but then
38
+ # cause a segfault later. Let's ensure that never happens.
39
+ $CFLAGS << " " << "-Werror-implicit-function-declaration"
40
+
37
41
  # Enable us to use """modern""" C
38
42
  if RUBY_VERSION < "2.4"
39
43
  $CFLAGS << " " << "-std=c99"
@@ -44,29 +48,56 @@ if RUBY_VERSION < "3"
44
48
  $defs << "-DCFUNC_FRAMES_BACKPORT_NEEDED"
45
49
  end
46
50
 
47
- # Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby 2.6
51
+ # Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby <= 2.6
48
52
  if RUBY_VERSION.start_with?("2.6")
49
53
  $defs << "-DCLASSPATH_BACKPORT_NEEDED"
50
54
  end
51
55
 
56
+ # Older Rubies don't have the MJIT header, see below for details
52
57
  if RUBY_VERSION < "2.6"
53
58
  $defs << "-DPRE_MJIT_RUBY"
54
59
  end
55
60
 
61
+ if RUBY_VERSION < "2.5"
62
+ $CFLAGS << " " << "-DPRE_EXECUTION_CONTEXT" # Flag that there's no execution context, we need to use threads instead
63
+ $CFLAGS << " " << "-Wno-attributes" # Silence a few warnings that we can't do anything about
64
+ end
65
+
66
+ if RUBY_VERSION < "2.4"
67
+ $CFLAGS << " " << "-DPRE_VM_ENV_RENAMES" # Flag that it's a really old Ruby, and a few constants were since renamed
68
+ end
69
+
56
70
  create_header
57
71
 
58
- # The Ruby MJIT header is always (afaik?) suffixed with the exact RUBY version,
59
- # including patch (e.g. 2.7.2). Thus, we add to the header file a definition
60
- # containing the exact file, so that it can be used in a #include in the C code.
61
- header_contents =
62
- File.read($extconf_h)
63
- .sub("#endif",
64
- <<~EXTCONF_H.strip
65
- #define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
66
-
67
- #endif
68
- EXTCONF_H
69
- )
70
- File.open($extconf_h, "w") { |file| file.puts(header_contents) }
71
-
72
- create_makefile "backtracie_native_extension"
72
+ if RUBY_VERSION < "2.6"
73
+ # Use the debase-ruby_core_source gem to get access to Ruby internal structures (no MJIT header -- the preferred
74
+ # option -- is available for these older Rubies)
75
+
76
+ require "debase/ruby_core_source"
77
+ dir_config("ruby") # allow user to pass in non-standard core include directory
78
+ if !Debase::RubyCoreSource.create_makefile_with_core(
79
+ proc { ["vm_core.h", "method.h", "iseq.h", "regenc.h"].map { |it| have_header(it) }.uniq == [true] },
80
+ "backtracie_native_extension"
81
+ )
82
+ raise "Error during native gem setup -- `Debase::RubyCoreSource.create_makefile_with_core` failed"
83
+ end
84
+
85
+ else
86
+ # Use MJIT header to get access to Ruby internal structures.
87
+
88
+ # The Ruby MJIT header is always (afaik?) suffixed with the exact RUBY version,
89
+ # including patch (e.g. 2.7.2). Thus, we add to the header file a definition
90
+ # containing the exact file, so that it can be used in a #include in the C code.
91
+ header_contents =
92
+ File.read($extconf_h)
93
+ .sub("#endif",
94
+ <<~EXTCONF_H.strip
95
+ #define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
96
+
97
+ #endif
98
+ EXTCONF_H
99
+ )
100
+ File.open($extconf_h, "w") { |file| file.puts(header_contents) }
101
+
102
+ create_makefile "backtracie_native_extension"
103
+ end
@@ -105,14 +105,31 @@
105
105
  #include "extconf.h"
106
106
 
107
107
  #ifndef PRE_MJIT_RUBY
108
-
109
108
  #ifndef RUBY_MJIT_HEADER_INCLUDED
110
109
  #define RUBY_MJIT_HEADER_INCLUDED
111
110
  #include RUBY_MJIT_HEADER
112
111
  #endif
112
+ #endif
113
113
 
114
114
  #include "ruby_shards.h"
115
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
+
116
133
  /**********************************************************************
117
134
  vm_backtrace.c -
118
135
  $Author: ko1 $
@@ -187,19 +204,24 @@ static int backtracie_rb_profile_frames_for_execution_context(
187
204
  // Initialize the raw_location, to avoid issues
188
205
  raw_locations[i].is_ruby_frame = false;
189
206
  raw_locations[i].should_use_iseq = false;
190
- raw_locations[i].should_use_cfunc_name = false;
191
207
  raw_locations[i].vm_method_type = 0;
192
208
  raw_locations[i].line_number = 0;
193
209
  raw_locations[i].iseq = Qnil;
194
210
  raw_locations[i].callable_method_entry = Qnil;
195
- raw_locations[i].cfunc_name = Qnil;
211
+ raw_locations[i].original_id = Qnil;
196
212
 
197
213
  // The current object this is getting called on!
198
214
  raw_locations[i].self = cfp->self;
199
215
 
200
216
  cme = rb_vm_frame_method_entry(cfp);
201
217
 
202
- if (VM_FRAME_RUBYFRAME_P(cfp)) {
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)) {
203
225
  raw_locations[i].is_ruby_frame = true;
204
226
  raw_locations[i].iseq = (VALUE) cfp->iseq;
205
227
 
@@ -223,6 +245,7 @@ static int backtracie_rb_profile_frames_for_execution_context(
223
245
  raw_locations[i].callable_method_entry = (VALUE) cme;
224
246
  raw_locations[i].vm_method_type = cme->def->type;
225
247
  raw_locations[i].line_number = 0;
248
+ raw_locations[i].original_id = ID2SYM(cme->def->original_id);
226
249
 
227
250
  i++;
228
251
  }
@@ -235,7 +258,12 @@ static int backtracie_rb_profile_frames_for_execution_context(
235
258
  }
236
259
 
237
260
  int backtracie_rb_profile_frames(int limit, raw_location *raw_locations) {
238
- return backtracie_rb_profile_frames_for_execution_context(GET_EC(), limit, raw_locations);
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
239
267
  }
240
268
 
241
269
  bool backtracie_is_thread_alive(VALUE thread) {
@@ -253,7 +281,11 @@ int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_locatio
253
281
  // the caller, otherwise I see a segfault in your future.
254
282
  rb_thread_t *thread_pointer = (rb_thread_t*) DATA_PTR(thread);
255
283
 
256
- return backtracie_rb_profile_frames_for_execution_context(thread_pointer->ec, limit, raw_locations);
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
257
289
  }
258
290
 
259
291
  VALUE backtracie_called_id(raw_location *the_location) {
@@ -273,10 +305,6 @@ VALUE backtracie_defined_class(raw_location *the_location) {
273
305
  ->defined_class;
274
306
  }
275
307
 
276
- VALUE backtracie_rb_vm_top_self() {
277
- return GET_VM()->top_self;
278
- }
279
-
280
308
  bool backtracie_iseq_is_block(raw_location *the_location) {
281
309
  if (the_location->iseq == Qnil) return false;
282
310
 
@@ -452,142 +480,54 @@ backported_rb_profile_frame_qualified_method_name(VALUE frame)
452
480
  }
453
481
  #endif // CLASSPATH_BACKPORT_NEEDED
454
482
 
455
- #endif // ifndef PRE_MJIT_RUBY
456
-
457
- // -----------------------------------------------------------------------------
458
-
459
483
  #ifdef PRE_MJIT_RUBY
460
484
 
461
- #include <stdbool.h>
462
-
463
- #include "ruby.h"
464
-
465
- #include "ruby_shards.h"
485
+ // A few extra functions copied from the Ruby sources, needed to support Ruby < 2.6
466
486
 
467
- typedef struct rb_backtrace_location_struct {
468
- enum LOCATION_TYPE {
469
- LOCATION_TYPE_ISEQ = 1,
470
- LOCATION_TYPE_ISEQ_CALCED,
471
- LOCATION_TYPE_CFUNC,
472
- } type;
473
-
474
- union {
475
- struct {
476
- const VALUE iseq; // Originally const rb_iseq_t *
477
- union {
478
- const VALUE *pc;
479
- int lineno;
480
- } lineno;
481
- } iseq;
482
- struct {
483
- ID mid;
484
- struct rb_backtrace_location_struct *prev_loc;
485
- } cfunc;
486
- } body;
487
- } rb_backtrace_location_t;
488
-
489
- struct valued_frame_info {
490
- rb_backtrace_location_t *loc;
491
- VALUE btobj;
492
- };
493
-
494
- // For Ruby < 2.6, we can't rely on the MJIT header. So we need to get... crafty. This alternative to
495
- // backtracie_rb_profile_frames_for_execution_context(...) piggybacks on the actual
496
- // Thread::Backtrace::Locations instances returned by Ruby's Thread#backtrace_locations, and then uses
497
- // knowledge of the VM internal layouts to get the data we need out of them.
498
- //
499
- // Currently, this approach gets us a lot less information than the Ruby >= 2.6 approach (e.g. currently we
500
- // get exactly the same as Thread::Backtrace::Locations offers), so it's a crappier replacement, but it does
501
- // get us support for older Rubies so it's better than nothing (to check the differences in behavior, check which
502
- // tests are disabled on < 2.6).
503
- int backtracie_profile_frames_from_ruby_locations(
504
- VALUE ruby_locations_array,
505
- raw_location *raw_locations
506
- ) {
507
- Check_Type(ruby_locations_array, T_ARRAY);
508
- int ruby_locations_array_size = RARRAY_LEN(ruby_locations_array);
509
-
510
- for (int i = 0; i < ruby_locations_array_size; i++) {
511
- // FIXME: We should validate that what we get out of the array is a Thread::Backtrace::Location instance
512
- VALUE location = rb_ary_entry(ruby_locations_array, i);
513
-
514
- // The leap of faith -- let's get into the data from the Location instance
515
- struct valued_frame_info *location_payload = DATA_PTR(location);
487
+ /**********************************************************************
488
+ vm_insnhelper.c - instruction helper functions.
489
+ $Author$
490
+ Copyright (C) 2007 Koichi Sasada
491
+ **********************************************************************/
516
492
 
517
- if (location_payload->loc->type == LOCATION_TYPE_ISEQ) {
518
- // Trigger calculation of the line number; this is calculated lazily when this method is invoked and
519
- // it turns the location type from LOCATION_TYPE_ISEQ to LOCATION_TYPE_ISEQ_CALCED.
520
- rb_funcall(location, rb_intern("lineno"), 0);
493
+ static rb_callable_method_entry_t *
494
+ check_method_entry(VALUE obj, int can_be_svar)
495
+ {
496
+ if (obj == Qfalse) return NULL;
521
497
 
522
- if (location_payload->loc->type == LOCATION_TYPE_ISEQ) {
523
- rb_raise(rb_eRuntimeError, "Internal error: Calling #lineno didn't turn a location into a LOCATION_TYPE_ISEQ_CALCED");
524
- }
525
- }
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
526
501
 
527
- if (location_payload->loc->type == LOCATION_TYPE_ISEQ || location_payload->loc->type == LOCATION_TYPE_ISEQ_CALCED) {
528
- // Ruby frame
529
- raw_locations[i].is_ruby_frame = true;
530
- raw_locations[i].should_use_iseq = true;
531
- raw_locations[i].should_use_cfunc_name = false;
532
- raw_locations[i].vm_method_type = -1;
533
- // FIXME: Poke MRI to always get the "calced" version, see location_lineno in vm_backtrace for the context
534
- raw_locations[i].line_number =
535
- location_payload->loc->type == LOCATION_TYPE_ISEQ_CALCED ?
536
- location_payload->loc->body.iseq.lineno.lineno : 0;
537
- raw_locations[i].iseq = (VALUE) location_payload->loc->body.iseq.iseq;
538
- raw_locations[i].callable_method_entry = Qnil;
539
- raw_locations[i].self = Qnil;
540
- raw_locations[i].cfunc_name = Qnil;
541
- } else {
542
- // cfunc frame
543
- raw_locations[i].is_ruby_frame = false;
544
- raw_locations[i].should_use_iseq = false;
545
- raw_locations[i].should_use_cfunc_name = true;
546
- raw_locations[i].vm_method_type = -1;
547
- raw_locations[i].line_number = 0;
548
- raw_locations[i].iseq = Qnil;
549
- raw_locations[i].callable_method_entry = Qnil;
550
- raw_locations[i].self = Qnil;
551
- raw_locations[i].cfunc_name = rb_id2str(location_payload->loc->body.cfunc.mid);
552
- }
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);
553
510
  }
554
-
555
- return ruby_locations_array_size;
556
- }
557
-
558
- VALUE backtracie_called_id(raw_location *the_location) {
559
- // Not available on PRE_MJIT_RUBY
560
- return Qnil;
561
- }
562
-
563
- VALUE backtracie_defined_class(raw_location *the_location) {
564
- // Not available on PRE_MJIT_RUBY
565
- return Qnil;
566
- }
567
-
568
- VALUE backtracie_rb_vm_top_self() {
569
- // This is only used on gem initialization so... Let's be a bit lazy :)
570
- return rb_eval_string("TOPLEVEL_BINDING.eval('self')");
571
- }
572
-
573
- bool backtracie_iseq_is_block(raw_location *the_location) {
574
- // Not available on PRE_MJIT_RUBY
575
- return false;
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
+ }
576
517
  }
577
518
 
578
- bool backtracie_iseq_is_eval(raw_location *the_location) {
579
- // Not available on PRE_MJIT_RUBY
580
- return false;
581
- }
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;
582
524
 
583
- VALUE backported_rb_profile_frame_method_name(VALUE frame) {
584
- // Not available on PRE_MJIT_RUBY
585
- return Qnil;
586
- }
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
+ }
587
529
 
588
- VALUE backtracie_refinement_name(raw_location *the_location) {
589
- // Not available on PRE_MJIT_RUBY
590
- return Qnil;
530
+ return check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
591
531
  }
592
532
 
593
- #endif // ifdef PRE_MJIT_RUBY
533
+ #endif
@@ -89,6 +89,11 @@
89
89
  Copyright (C) 2009 Koichi Sasada
90
90
  **********************************************************************/
91
91
 
92
+ #ifdef PRE_MJIT_RUBY
93
+ #include <stdbool.h>
94
+ #include <vm_core.h>
95
+ #include <method.h>
96
+ #else
92
97
  #ifndef RUBY_MJIT_HEADER_INCLUDED
93
98
  typedef enum {
94
99
  VM_METHOD_TYPE_ISEQ, /*!< Ruby method */
@@ -104,10 +109,7 @@
104
109
  VM_METHOD_TYPE_MISSING, /*!< wrapper for method_missing(id) */
105
110
  VM_METHOD_TYPE_REFINED, /*!< refinement */
106
111
  } rb_method_type_t;
107
- #define VM_METHOD_TYPE_MINIMUM_BITS 4
108
-
109
- // Needed for Ruby 2.6 as this is not defined on any public header
110
- void rb_hash_bulk_insert(long, const VALUE *, VALUE);
112
+ #endif
111
113
  #endif
112
114
 
113
115
  // -----------------------------------------------------------------------------
@@ -118,32 +120,24 @@ typedef struct {
118
120
  // for ruby frames where the callable_method_entry is not of type VM_METHOD_TYPE_ISEQ, most of the metadata we
119
121
  // want can be found by querying the iseq, and there may not even be an callable_method_entry
120
122
  unsigned int should_use_iseq : 1;
121
- unsigned int should_use_cfunc_name : 1;
122
123
 
123
- rb_method_type_t vm_method_type : VM_METHOD_TYPE_MINIMUM_BITS;
124
+ rb_method_type_t vm_method_type;
124
125
  int line_number;
125
126
  VALUE iseq;
126
127
  VALUE callable_method_entry;
127
128
  VALUE self;
128
- VALUE cfunc_name;
129
+ VALUE original_id;
129
130
  } raw_location;
130
131
 
131
- #ifndef PRE_MJIT_RUBY
132
- int backtracie_rb_profile_frames(int limit, raw_location *raw_locations);
133
- int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_location *raw_locations);
134
- bool backtracie_is_thread_alive(VALUE thread);
135
- #endif
132
+ int backtracie_rb_profile_frames(int limit, raw_location *raw_locations);
133
+ int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_location *raw_locations);
134
+ bool backtracie_is_thread_alive(VALUE thread);
136
135
  VALUE backtracie_called_id(raw_location *the_location);
137
136
  VALUE backtracie_defined_class(raw_location *the_location);
138
- VALUE backtracie_rb_vm_top_self();
139
137
  bool backtracie_iseq_is_block(raw_location *the_location);
140
138
  bool backtracie_iseq_is_eval(raw_location *the_location);
141
139
  VALUE backtracie_refinement_name(raw_location *the_location);
142
140
 
143
- #ifdef PRE_MJIT_RUBY
144
- int backtracie_profile_frames_from_ruby_locations(VALUE ruby_locations_array, raw_location *raw_locations);
145
- #endif
146
-
147
141
  // -----------------------------------------------------------------------------
148
142
 
149
143
  // Ruby 3.0 finally added support for showing "cfunc frames" (frames for methods written in C) in stack traces:
@@ -159,7 +153,7 @@ VALUE backtracie_refinement_name(raw_location *the_location);
159
153
  #define backtracie_rb_profile_frame_method_name rb_profile_frame_method_name
160
154
  #endif
161
155
 
162
- // Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby 2.6
156
+ // Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby <= 2.6
163
157
  // The interesting bit is actually the fix to rb_profile_frame_classpath BUT since rb_profile_frame_qualified_method_name
164
158
  // internally relies on rb_profile_frame_classpath we also need to add a copy of that one as well.
165
159
  #ifdef CLASSPATH_BACKPORT_NEEDED
@@ -19,5 +19,5 @@
19
19
  # along with backtracie. If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
21
  module Backtracie
22
- VERSION = "0.2.2"
22
+ VERSION = "0.3.0"
23
23
  end
data/lib/backtracie.rb CHANGED
@@ -28,8 +28,17 @@ require "backtracie_native_extension"
28
28
  module Backtracie
29
29
  module_function
30
30
 
31
- def caller_locations
32
- Primitive.caller_locations
31
+ if RUBY_VERSION < "2.5"
32
+ def caller_locations
33
+ # FIXME: We're having some trouble getting the current thread on older Rubies, see the FIXME on
34
+ # backtracie_rb_profile_frames. A workaround is to just pass in the reference to the current thread explicitly
35
+ # (and slice off a few frames, since caller_locations is supposed to start from the caller of our caller)
36
+ backtrace_locations(Thread.current)[3..-1]
37
+ end
38
+ else
39
+ def caller_locations
40
+ Primitive.caller_locations
41
+ end
33
42
  end
34
43
 
35
44
  # Defined via native code only; not redirecting via Primitive to avoid an extra stack frame on the stack
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backtracie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivo Anjo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-09-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: debase-ruby_core_source
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.10.12
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.10'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.10.12
13
33
  description: Ruby gem for beautiful backtraces
14
34
  email:
15
35
  - ivo@ivoanjo.me
@@ -51,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
71
  - !ruby/object:Gem::Version
52
72
  version: '0'
53
73
  requirements: []
54
- rubygems_version: 3.0.9
74
+ rubygems_version: 3.1.4
55
75
  signing_key:
56
76
  specification_version: 4
57
77
  summary: Ruby gem for beautiful backtraces