rbtrace 0.5.4 → 0.5.5

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: ffdcce16c0cb92399ccb6684be8b23628703d420786b53ed035164d184e72488
4
- data.tar.gz: 987b982955f4af23e247d4155a9ff134bdf2231e5443024a5357c7df14f581ef
3
+ metadata.gz: 9628477734dcb6ba5d9e02abbcb0c599b74764cf1b4cf385958adc77caafc21b
4
+ data.tar.gz: 9fe0cc77c6f3dc2a98e7890bbefae1058f7a1f5258d3f3443785ca970d524496
5
5
  SHA512:
6
- metadata.gz: 536787701408f0763ccf9345e6e4715cc2bf057e99f6be69d00ad431dc7685898267a2a76235d29875128711c87192d91e1b281dbe889579779321da66b3c1ca
7
- data.tar.gz: 1fc1fd4f82e5ac0808e7f95f518a2f989902c7fe424a9a67f63632aca41e6a47a405a46a77022b618f32664b476a5b550e8e4d0aa1c27fe630e3916d82b27c84
6
+ metadata.gz: b487fa9ff0ed5b62d9a030057f72e8a55279b2ede600a27376895f3497a0f073882ff2ef536c0d92a960703207b8337b1cb07245a23b8e4b1e856c8bef55a961
7
+ data.tar.gz: a9286d573871f902c7a73f1b9f9eaa5138d84835c7849bbd37ee460678f0cb02412fb0ce7d8760d1f0accdf20120cf6370147b4521dffd4a662964ae0eb72f5d
@@ -10,7 +10,7 @@ jobs:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: ["ubuntu-latest"]
13
- ruby: ["4.0", "3.4", "3.3", "3.2"]
13
+ ruby: ["ruby-head", "4.0", "3.4", "3.3", "3.2"]
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - name: Check out code
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ - 0.5.5 - 07-08-2026
2
+
3
+ - Add Ruby 4.1 support by using the current postponed-job and block callback APIs
4
+ - Fix native command handling to validate MessagePack input, release native memory, and reject invalid tracer IDs
5
+ - Fix traced expressions to avoid the deprecated ObjectSpace._id2ref API
6
+ - Fix --heapdump and --shapesdump by loading objspace before using its dump APIs
7
+
1
8
  - 0.5.4 - 19-05-2026
2
9
 
3
10
  - Fix --interactive irb mode not printing eval results
@@ -5,21 +12,20 @@
5
12
  - 0.5.3 - 03-12-2025
6
13
 
7
14
  - Fix --interactive irb mode
8
- - Fix: Build failures with C23 (#100)
9
15
 
10
- - 0.5.2 - 05-13-2025
16
+ - 0.5.2 - 13-05-2025
11
17
 
12
18
  - Specify void* argument in rbtrace_gc_mark()'s signature, as expected by rb_data_type_t.
13
19
  This was a warning before C23 but it's now an error, and GCC 15 defaults to C23.
14
20
 
15
21
  - 0.5.1 - 19-12-2023
16
22
 
17
- - Fix: RbTrace was used incorrectly instead of RBTrace, which lead to a broken build
23
+ - Fix: RbTrace was used incorrectly instead of RBTrace, which led to a broken build
18
24
 
19
25
  - 0.5.0 - 02-12-2023
20
26
 
21
27
  - Improved --backtraces implementation
22
- - Added --shapedump which is useful for lighter weight object shape dumps on Ruby 3.2 and up
28
+ - Added --shapesdump which is useful for lighter weight object shape dumps on Ruby 3.2 and up
23
29
  - Move heap dumping to a fork for better performance
24
30
  - Use TypedData API which has been supported for a very long time
25
31
  - Update CI and fix some small errors
data/ext/extconf.rb CHANGED
@@ -61,6 +61,8 @@ end
61
61
 
62
62
  have_func('rb_during_gc', 'ruby.h')
63
63
  have_func('rb_gc_add_event_hook', ['ruby.h', 'node.h'])
64
+ have_func('rb_postponed_job_preregister', ['ruby.h', 'ruby/debug.h'])
65
+ have_func('rb_postponed_job_trigger', ['ruby.h', 'ruby/debug.h'])
64
66
  have_func('rb_postponed_job_register_one', 'ruby.h')
65
67
 
66
68
  # warnings save lives
data/ext/rbtrace.c CHANGED
@@ -307,6 +307,18 @@ rbtrace__send_names(ID mid, VALUE klass)
307
307
  }
308
308
  }
309
309
 
310
+ typedef struct {
311
+ VALUE proc;
312
+ VALUE receiver;
313
+ } expr_proc_call_t;
314
+
315
+ static VALUE
316
+ call_expr_proc(VALUE data)
317
+ {
318
+ expr_proc_call_t *args = (expr_proc_call_t *)data;
319
+ return rb_funcall(args->proc, rb_intern("call"), 1, args->receiver);
320
+ }
321
+
310
322
  static int in_event_hook = 0;
311
323
 
312
324
  static void
@@ -491,8 +503,16 @@ event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
491
503
  val = rb_inspect(rb_ivar_get(self, rb_intern(expr)));
492
504
 
493
505
  } else {
494
- snprintf(buffer, len+150, "(begin; ObjectSpace._id2ref(%ld).instance_eval{ %s }; rescue Exception => e; e; end).inspect", NUM2LONG(rb_obj_id(self)), expr);
495
- val = rb_eval_string_protect(buffer, 0);
506
+ int state = 0;
507
+ VALUE expr_proc;
508
+ expr_proc_call_t args;
509
+ snprintf(buffer, len+150, "proc { |__rbtrace_receiver__| (begin; __rbtrace_receiver__.instance_eval { %s }; rescue Exception => e; e; end).inspect }", expr);
510
+ expr_proc = rb_eval_string_protect(buffer, &state);
511
+ if (state == 0) {
512
+ args.proc = expr_proc;
513
+ args.receiver = self;
514
+ val = rb_protect(call_expr_proc, (VALUE)&args, &state);
515
+ }
496
516
  }
497
517
 
498
518
  if (RTEST(val) && TYPE(val) == T_STRING) {
@@ -590,10 +610,12 @@ rbtracer_remove(char *query, int id)
590
610
  }
591
611
  }
592
612
  } else {
593
- if (id >= MAX_TRACERS) goto out;
613
+ if (id < 0 || id >= MAX_TRACERS) goto out;
594
614
  tracer = &rbtracer.list[id];
595
615
  }
596
616
 
617
+ if (!tracer) goto out;
618
+
597
619
  if (tracer->query) {
598
620
  tracer_id = tracer->id;
599
621
  tracer->mid = 0;
@@ -735,10 +757,16 @@ rbtracer_add(char *query, bool is_slow)
735
757
  goto out;
736
758
  }
737
759
 
760
+ char *query_copy = strdup(query);
761
+ if (!query_copy) {
762
+ tracer_id = -1;
763
+ goto out;
764
+ }
765
+
738
766
  memset(tracer, 0, sizeof(*tracer));
739
767
 
740
768
  tracer->id = tracer_id;
741
- tracer->query = strdup(query);
769
+ tracer->query = query_copy;
742
770
  tracer->is_slow = is_slow;
743
771
 
744
772
  if (klass_end != klass_begin) {
@@ -774,15 +802,19 @@ rbtracer_add_expr(int id, char *expr)
774
802
  int tracer_id = -1;
775
803
  rbtracer_t *tracer = NULL;
776
804
 
777
- if (id >= MAX_TRACERS) goto out;
805
+ if (id < 0 || id >= MAX_TRACERS) goto out;
778
806
  tracer = &rbtracer.list[id];
779
807
 
780
808
  if (tracer->query) {
781
809
  tracer_id = tracer->id;
782
810
 
783
811
  if (tracer->num_exprs < MAX_EXPRS) {
784
- expr_id = tracer->num_exprs++;
785
- tracer->exprs[expr_id] = strdup(expr);
812
+ char *expr_copy = strdup(expr);
813
+ if (!expr_copy) goto out;
814
+
815
+ expr_id = tracer->num_exprs;
816
+ tracer->exprs[expr_id] = expr_copy;
817
+ tracer->num_exprs++;
786
818
  }
787
819
  }
788
820
 
@@ -1043,6 +1075,20 @@ rbtrace__process_event(msgpack_object cmd)
1043
1075
  }
1044
1076
  }
1045
1077
 
1078
+ static VALUE
1079
+ rbtrace__process_unpacked_event(VALUE data)
1080
+ {
1081
+ rbtrace__process_event(*(msgpack_object *)data);
1082
+ return Qnil;
1083
+ }
1084
+
1085
+ static VALUE
1086
+ rbtrace__destroy_unpacked(VALUE data)
1087
+ {
1088
+ msgpack_unpacked_destroy((msgpack_unpacked *)data);
1089
+ return Qnil;
1090
+ }
1091
+
1046
1092
  static void
1047
1093
  rbtrace__receive(void *data)
1048
1094
  {
@@ -1060,7 +1106,7 @@ rbtrace__receive(void *data)
1060
1106
  int n = 0;
1061
1107
 
1062
1108
  while (true) {
1063
- int ret = -1;
1109
+ ssize_t ret = -1;
1064
1110
 
1065
1111
  for (n=0; n<10 && ret==-1; n++)
1066
1112
  ret = msgrcv(rbtracer.mqi_id, &msg, sizeof(msg)-sizeof(long), 0, IPC_NOWAIT);
@@ -1071,10 +1117,17 @@ rbtrace__receive(void *data)
1071
1117
  msgpack_unpacked unpacked;
1072
1118
  msgpack_unpacked_init(&unpacked);
1073
1119
 
1074
- bool success = msgpack_unpack_next(&unpacked, msg.buf, sizeof(msg.buf), NULL);
1075
- if (!success) continue;
1120
+ msgpack_unpack_return unpack_result = msgpack_unpack_next(&unpacked, msg.buf, (size_t)ret, NULL);
1121
+ if (unpack_result != MSGPACK_UNPACK_SUCCESS &&
1122
+ unpack_result != MSGPACK_UNPACK_EXTRA_BYTES) {
1123
+ msgpack_unpacked_destroy(&unpacked);
1124
+ continue;
1125
+ }
1076
1126
 
1077
- rbtrace__process_event(unpacked.data);
1127
+ rb_ensure(
1128
+ rbtrace__process_unpacked_event, (VALUE)&unpacked.data,
1129
+ rbtrace__destroy_unpacked, (VALUE)&unpacked
1130
+ );
1078
1131
  }
1079
1132
  }
1080
1133
  }
@@ -1092,11 +1145,20 @@ rbtrace_gc_mark(void *ptr)
1092
1145
 
1093
1146
  static VALUE gc_hook;
1094
1147
 
1095
- #if defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE) || !defined(RUBY_VM)
1148
+ #if defined(HAVE_RB_POSTPONED_JOB_PREREGISTER) && defined(HAVE_RB_POSTPONED_JOB_TRIGGER)
1149
+ #define RBTRACE_USE_PREREGISTERED_POSTPONED_JOB 1
1150
+ static rb_postponed_job_handle_t rbtrace_postponed_job_handle = POSTPONED_JOB_HANDLE_INVALID;
1151
+ #endif
1152
+
1153
+ #if defined(RBTRACE_USE_PREREGISTERED_POSTPONED_JOB) || defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE) || !defined(RUBY_VM)
1096
1154
  static void
1097
1155
  sigurg(int signal)
1098
1156
  {
1099
- #if defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE)
1157
+ #if defined(RBTRACE_USE_PREREGISTERED_POSTPONED_JOB)
1158
+ if (rbtrace_postponed_job_handle != POSTPONED_JOB_HANDLE_INVALID) {
1159
+ rb_postponed_job_trigger(rbtrace_postponed_job_handle);
1160
+ }
1161
+ #elif defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE)
1100
1162
  rb_postponed_job_register_one(0, rbtrace__receive, 0);
1101
1163
  #else
1102
1164
  rbtrace__receive(0);
@@ -1104,10 +1166,10 @@ sigurg(int signal)
1104
1166
  }
1105
1167
  #endif
1106
1168
 
1107
- #if !defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE) && defined(RUBY_VM)
1169
+ #if defined(RUBY_VM) && (defined(RBTRACE_USE_PREREGISTERED_POSTPONED_JOB) || !defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE))
1108
1170
  static VALUE signal_handler_proc;
1109
1171
  static VALUE
1110
- signal_handler_wrapper(VALUE arg, VALUE ctx)
1172
+ signal_handler_wrapper(RB_BLOCK_CALL_FUNC_ARGLIST(arg, ctx))
1111
1173
  {
1112
1174
  static int in_signal_handler = 0;
1113
1175
  if (in_signal_handler) return Qnil;
@@ -1118,6 +1180,14 @@ signal_handler_wrapper(VALUE arg, VALUE ctx)
1118
1180
 
1119
1181
  return Qnil;
1120
1182
  }
1183
+
1184
+ static void
1185
+ install_ruby_sigurg_handler(void)
1186
+ {
1187
+ signal_handler_proc = rb_proc_new(signal_handler_wrapper, Qnil);
1188
+ rb_global_variable(&signal_handler_proc);
1189
+ rb_funcall(Qnil, rb_intern("trap"), 2, rb_str_new_cstr("URG"), signal_handler_proc);
1190
+ }
1121
1191
  #endif
1122
1192
 
1123
1193
  static VALUE
@@ -1154,12 +1224,25 @@ Init_rbtrace()
1154
1224
  gc_hook = TypedData_Wrap_Struct(rb_cObject, &rbtrace_type, NULL);
1155
1225
 
1156
1226
  // catch signal telling us to read from the msgq
1157
- #if defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE)
1227
+ #if defined(RBTRACE_USE_PREREGISTERED_POSTPONED_JOB)
1228
+ rbtrace_postponed_job_handle = rb_postponed_job_preregister(0, rbtrace__receive, 0);
1229
+ if (rbtrace_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID) {
1230
+
1231
+ #ifdef RUBY_VM
1232
+ rb_warn("rbtrace: failed to preregister postponed job; falling back to Ruby SIGURG handler");
1233
+ install_ruby_sigurg_handler();
1234
+ #else
1235
+ rb_raise(rb_eRuntimeError, "rbtrace: failed to preregister postponed job");
1236
+ #endif
1237
+
1238
+ } else {
1239
+ signal(SIGURG, sigurg);
1240
+ }
1241
+
1242
+ #elif defined(HAVE_RB_POSTPONED_JOB_REGISTER_ONE)
1158
1243
  signal(SIGURG, sigurg);
1159
1244
  #elif defined(RUBY_VM)
1160
- signal_handler_proc = rb_proc_new(signal_handler_wrapper, Qnil);
1161
- rb_global_variable(&signal_handler_proc);
1162
- rb_funcall(Qnil, rb_intern("trap"), 2, rb_str_new_cstr("URG"), signal_handler_proc);
1245
+ install_ruby_sigurg_handler();
1163
1246
  #else
1164
1247
  signal(SIGURG, sigurg);
1165
1248
  #endif
data/lib/rbtrace/cli.rb CHANGED
@@ -243,7 +243,7 @@ EOS
243
243
  ARGV.clear
244
244
  end
245
245
 
246
- unless %w[ fork eval interactive backtrace backtraces slow slowcpu firehose methods config gc memory heapdump].find{ |n| opts[:"#{n}_given"] }
246
+ unless %w[ fork eval interactive backtrace backtraces slow slowcpu firehose methods config gc memory heapdump shapesdump].find{ |n| opts[:"#{n}_given"] }
247
247
  $stderr.puts "Error: --slow, --slowcpu, --gc, --firehose, --methods, --interactive, --backtraces, --backtrace, --memory, --heapdump, --shapesdump or --config required."
248
248
  $stderr.puts "Try --help for help."
249
249
  exit(-1)
@@ -505,6 +505,7 @@ EOS
505
505
  tracer.eval(<<-RUBY)
506
506
  Thread.new do
507
507
  Thread.current.name = '__RBTrace__'
508
+ require 'objspace'
508
509
  pid = ::Process.fork do
509
510
  file = File.open('#{filename}.tmp', 'w')
510
511
  ObjectSpace.dump_all(output: file)
@@ -531,6 +532,7 @@ EOS
531
532
  tracer.eval(<<-RUBY)
532
533
  Thread.new do
533
534
  Thread.current.name = '__RBTrace__'
535
+ require 'objspace'
534
536
  pid = ::Process.fork do
535
537
  file = File.open('#{filename}.tmp', 'w')
536
538
  ObjectSpace.dump_shapes(output: file)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RBTracer
4
- VERSION = "0.5.4"
4
+ VERSION = "0.5.5"
5
5
  end
data/server.rb CHANGED
@@ -32,6 +32,7 @@ while true
32
32
  Dir.pwd
33
33
  Process.pid
34
34
  'hello'.multiply_vowels(3){ :ohai }
35
+ 'hello'.upcase
35
36
  sleep rand*0.5
36
37
 
37
38
  ENV['blah']
data/test.sh CHANGED
@@ -15,10 +15,13 @@ export RUBYOPT="-I.:lib"
15
15
  bundle exec ruby server.rb &
16
16
  export PID=$!
17
17
 
18
- trap cleanup INT TERM
18
+ trap cleanup EXIT INT TERM
19
19
  cleanup() {
20
- kill $PID
21
- wait $PID || true
20
+ if [ -n "${PID:-}" ]; then
21
+ kill "$PID" 2>/dev/null || true
22
+ wait "$PID" 2>/dev/null || true
23
+ PID=
24
+ fi
22
25
  }
23
26
 
24
27
  trace() {
@@ -32,13 +35,84 @@ trace() {
32
35
  echo
33
36
  }
34
37
 
38
+ assert_trace() {
39
+ expected="$1"
40
+ shift
41
+
42
+ echo ------------------------------------------
43
+ echo ./bin/rbtrace -p $PID "$@"
44
+ echo ------------------------------------------
45
+ if ! output=$(bundle exec ./bin/rbtrace -p $PID "$@" 2>&1); then
46
+ echo "$output"
47
+ return 1
48
+ fi
49
+ echo "$output"
50
+ echo "$output" | grep -F "$expected" >/dev/null
51
+ echo
52
+ }
53
+
54
+ assert_dump() {
55
+ dump_type="$1"
56
+ dump=$(mktemp)
57
+ rm -f "$dump"
58
+
59
+ echo ------------------------------------------
60
+ echo ./bin/rbtrace -p $PID --"$dump_type"="$dump"
61
+ echo ------------------------------------------
62
+ if ! output=$(bundle exec ./bin/rbtrace -p $PID --"$dump_type"="$dump" 2>&1); then
63
+ echo "$output"
64
+ rm -f "$dump" "$dump.tmp"
65
+ return 1
66
+ fi
67
+ echo "$output"
68
+
69
+ tries=0
70
+ while [ "$tries" -lt 50 ] && [ ! -s "$dump" ]; do
71
+ sleep 0.1
72
+ tries=$((tries + 1))
73
+ done
74
+
75
+ if [ ! -s "$dump" ]; then
76
+ echo "Expected $dump_type to be written to $dump"
77
+ rm -f "$dump" "$dump.tmp"
78
+ return 1
79
+ fi
80
+
81
+ rm -f "$dump" "$dump.tmp"
82
+ echo
83
+ }
84
+
85
+ assert_timed_trace() {
86
+ expected="$1"
87
+ shift
88
+ output=$(mktemp)
89
+
90
+ echo ------------------------------------------
91
+ echo ./bin/rbtrace -p $PID "$@"
92
+ echo ------------------------------------------
93
+ bundle exec ./bin/rbtrace -p $PID "$@" >"$output" 2>&1 &
94
+ tracer_pid=$!
95
+ sleep 2
96
+ kill "$tracer_pid" 2>/dev/null || true
97
+ wait "$tracer_pid" 2>/dev/null || true
98
+ cat "$output"
99
+ if ! grep -F "$expected" "$output" >/dev/null; then
100
+ rm -f "$output"
101
+ return 1
102
+ fi
103
+ rm -f "$output"
104
+ echo
105
+ }
106
+
35
107
  trace -m Test.run --devmode
36
108
  trace -m sleep
37
109
  trace -m sleep Dir.chdir Dir.pwd Process.pid "String#gsub" "String#*"
38
110
  trace -m "Kernel#"
39
111
  trace -m "String#gsub(self,@test)" "String#*(self,__source__)" "String#multiply_vowels(self,self.length,num)"
40
- trace -e 'p(1 + 1)'
41
- trace -h
112
+ assert_timed_trace 'String#upcase(self.class=String)' -m 'String#upcase(self.class)'
113
+ assert_trace '=> 2' -e 'p(1 + 1)'
114
+ assert_dump heapdump
115
+ assert_dump shapesdump
42
116
  trace --gc --slow=200
43
117
  trace --gc -m Dir.
44
118
  trace --slow=250
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbtrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
@@ -139,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
139
  - !ruby/object:Gem::Version
140
140
  version: '0'
141
141
  requirements: []
142
- rubygems_version: 4.0.6
142
+ rubygems_version: 3.6.9
143
143
  specification_version: 4
144
144
  summary: 'rbtrace: like strace but for ruby code'
145
145
  test_files: []