backtracie 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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