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.
@@ -0,0 +1,20 @@
1
+ #ifndef BACKTRACIE_PRIVATE_H
2
+ #define BACKTRACIE_PRIVATE_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+
7
+ // Need to define an assert macro - we might have just used RUBY_ASSERT, but
8
+ // that's not exported in Ruby < 2.7.
9
+ #define BACKTRACIE_ASSERT(expr) BACKTRACIE_ASSERT_MSG((expr), (#expr))
10
+ #define BACKTRACIE_ASSERT_MSG(expr, msg) \
11
+ do { \
12
+ if ((expr) == 0) { \
13
+ rb_bug("backtracie gem: %s:%d: %s", __FILE__, __LINE__, msg); \
14
+ } \
15
+ } while (0)
16
+ #define BACKTRACIE_ASSERT_FAIL(msg) BACKTRACIE_ASSERT_MSG(0, msg)
17
+
18
+ bool backtracie_is_thread_alive(VALUE thread);
19
+ void backtracie_init_c_test_helpers(VALUE backtracie_module);
20
+ #endif
@@ -0,0 +1,62 @@
1
+ #include "backtracie_private.h"
2
+ #include "public/backtracie.h"
3
+
4
+ #include <ruby.h>
5
+ #include <ruby/thread.h>
6
+
7
+ static VALUE backtracie_backtrace_from_thread(VALUE self);
8
+ static VALUE backtracie_backtrace_from_thread_cthread(void *ctx);
9
+ static VALUE stdlib_backtrace_from_thread(VALUE self);
10
+ static VALUE stdlib_backtrace_from_thread_cthread(void *ctx);
11
+ static VALUE backtracie_backtrace_from_empty_thread(VALUE self);
12
+ static VALUE backtracie_backtrace_from_empty_thread_cthread(void *ctx);
13
+
14
+ void backtracie_init_c_test_helpers(VALUE backtracie_module) {
15
+ VALUE test_helpers_mod =
16
+ rb_define_module_under(backtracie_module, "TestHelpers");
17
+ rb_define_singleton_method(test_helpers_mod,
18
+ "backtracie_backtrace_from_thread",
19
+ backtracie_backtrace_from_thread, 0);
20
+ rb_define_singleton_method(test_helpers_mod, "stdlib_backtrace_from_thread",
21
+ stdlib_backtrace_from_thread, 0);
22
+ rb_define_singleton_method(test_helpers_mod,
23
+ "backtracie_backtrace_from_empty_thread",
24
+ backtracie_backtrace_from_empty_thread, 0);
25
+ }
26
+
27
+ static VALUE backtracie_backtrace_from_thread(VALUE self) {
28
+ VALUE th = rb_thread_create(backtracie_backtrace_from_thread_cthread, NULL);
29
+ return rb_funcall(th, rb_intern("value"), 0);
30
+ }
31
+
32
+ static VALUE backtracie_backtrace_from_thread_cthread(void *ctx) {
33
+ VALUE backtracie_mod = rb_const_get(rb_cObject, rb_intern("Backtracie"));
34
+ return rb_funcall(backtracie_mod, rb_intern("backtrace_locations"), 1,
35
+ rb_thread_current());
36
+ }
37
+
38
+ static VALUE stdlib_backtrace_from_thread(VALUE self) {
39
+ VALUE th = rb_thread_create(stdlib_backtrace_from_thread_cthread, NULL);
40
+ return rb_funcall(th, rb_intern("value"), 0);
41
+ }
42
+
43
+ static VALUE stdlib_backtrace_from_thread_cthread(void *ctx) {
44
+ return rb_funcall(rb_thread_current(), rb_intern("backtrace_locations"), 0);
45
+ }
46
+
47
+ static VALUE backtracie_backtrace_from_empty_thread(VALUE self) {
48
+ VALUE backtracie_mod = rb_const_get(rb_cObject, rb_intern("Backtracie"));
49
+ VALUE th =
50
+ rb_thread_create(backtracie_backtrace_from_empty_thread_cthread, NULL);
51
+ VALUE bt =
52
+ rb_funcall(backtracie_mod, rb_intern("backtrace_locations"), 1, th);
53
+ rb_thread_kill(th);
54
+ rb_funcall(th, rb_intern("join"), 0);
55
+ return bt;
56
+ }
57
+
58
+ static VALUE backtracie_backtrace_from_empty_thread_cthread(void *ctx) {
59
+ // yield the GVL without creating a frame of any kind
60
+ rb_thread_sleep(-1);
61
+ return Qnil;
62
+ }
@@ -18,7 +18,7 @@
18
18
  # You should have received a copy of the GNU Lesser General Public License
19
19
  # along with backtracie. If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
- if ["jruby", "truffleruby"].include?(RUBY_ENGINE)
21
+ if %w[jruby truffleruby].include?(RUBY_ENGINE)
22
22
  raise \
23
23
  "\n#{"-" * 80}\nSorry! This gem is unsupported on #{RUBY_ENGINE}. Since it relies on a lot of guts of MRI Ruby, " \
24
24
  "it's impossible to make a direct port.\n" \
@@ -38,28 +38,20 @@ $CFLAGS << " " << "-Wno-declaration-after-statement"
38
38
  # cause a segfault later. Let's ensure that never happens.
39
39
  $CFLAGS << " " << "-Werror-implicit-function-declaration"
40
40
 
41
- # Enable us to use """modern""" C
42
- if RUBY_VERSION < "2.4"
43
- $CFLAGS << " " << "-std=c99"
44
- end
41
+ # Enable us to use """modern""" C. Note that Ruby requires GNU extensions; specifically,
42
+ # NSIG is expected to be defined in signal.h.
43
+ $CFLAGS << " " << "-std=gnu99" if RUBY_VERSION < "2.4"
45
44
 
46
- # On older Rubies, we need to enable a few backports. See cfunc_frames_backport.h for details.
47
- if RUBY_VERSION < "3"
48
- $defs << "-DCFUNC_FRAMES_BACKPORT_NEEDED"
49
- end
45
+ $CFLAGS << " " << "-DPRE_RB_ISEQ_TYPE" if RUBY_VERSION < "3.2"
50
46
 
51
- # Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby <= 2.6
52
- if RUBY_VERSION.start_with?("2.6")
53
- $defs << "-DCLASSPATH_BACKPORT_NEEDED"
54
- end
47
+ $CFLAGS << " " << "-DPRE_GC_MARK_MOVABLE" if RUBY_VERSION < "2.7"
55
48
 
56
49
  # Older Rubies don't have the MJIT header, see below for details
57
- if RUBY_VERSION < "2.6"
58
- $defs << "-DPRE_MJIT_RUBY"
59
- end
50
+ $defs << "-DPRE_MJIT_RUBY" if RUBY_VERSION < "2.6"
60
51
 
61
52
  if RUBY_VERSION < "2.5"
62
53
  $CFLAGS << " " << "-DPRE_EXECUTION_CONTEXT" # Flag that there's no execution context, we need to use threads instead
54
+ $CFLAGS << " " << "-DPRE_LOCATION_PATHOBJ"
63
55
  $CFLAGS << " " << "-Wno-attributes" # Silence a few warnings that we can't do anything about
64
56
  end
65
57
 
@@ -67,6 +59,8 @@ if RUBY_VERSION < "2.4"
67
59
  $CFLAGS << " " << "-DPRE_VM_ENV_RENAMES" # Flag that it's a really old Ruby, and a few constants were since renamed
68
60
  end
69
61
 
62
+ $CFLAGS << " " << "-DBACKTRACIE_EXPORTS"
63
+ append_cflags ["-fvisibility=hidden"]
70
64
  create_header
71
65
 
72
66
  if RUBY_VERSION < "2.6"
@@ -75,7 +69,7 @@ if RUBY_VERSION < "2.6"
75
69
 
76
70
  require "debase/ruby_core_source"
77
71
  dir_config("ruby") # allow user to pass in non-standard core include directory
78
- if !Debase::RubyCoreSource.create_makefile_with_core(
72
+ unless Debase::RubyCoreSource.create_makefile_with_core(
79
73
  proc { ["vm_core.h", "method.h", "iseq.h", "regenc.h"].map { |it| have_header(it) }.uniq == [true] },
80
74
  "backtracie_native_extension"
81
75
  )
@@ -0,0 +1,268 @@
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
+ #ifndef BACKTRACIE_PUBLIC_H
20
+ #define BACKTRACIE_PUBLIC_H
21
+
22
+ #include <ruby.h>
23
+ #include <stdbool.h>
24
+ #include <stdint.h>
25
+
26
+ // clang-format off
27
+ #if defined(_WIN32) || defined(_WIN64)
28
+ # ifdef BACKTRACIE_EXPORTS
29
+ # define BACKTRACIE_API __declspec(dllexport)
30
+ # else
31
+ # define BACKTRACIE_API __declspec(dllimport)
32
+ # endif
33
+ #else
34
+ # define BACKTRACIE_API __attribute__((visibility("default")))
35
+ #endif
36
+ // clang-format on
37
+
38
+ typedef struct {
39
+ // The first element of this struct is a bitfield, comprised of uint32_t's.
40
+ // Ruby requires that VALUE be at least 32-bit but not necessarily 64-bit;
41
+ // using uint32_t for the bitfield ensures we have a minimum of pointless
42
+ // padding in this struct no matter the system.
43
+
44
+ // 1 -> ruby frame / 0 -> cfunc frame
45
+ uint32_t is_ruby_frame : 1;
46
+ // 1 means self really is the self object, 0 means it's the class of the self.
47
+ uint32_t self_is_real_self : 1;
48
+ // The iseq & cme; one, both, or neither might actually be available; if
49
+ // they're not, they will be Qnil. These need to be GC marked if you wish to
50
+ // keep the location alive.
51
+ VALUE iseq;
52
+ VALUE callable_method_entry;
53
+ // This is either self, or rb_class_of(self), depending on a few thing.
54
+ // It will be the actual self object for the frame if
55
+ // - self is the toplevel binding,
56
+ // - self is rb_mRubyVMFrozenCore,
57
+ // - self is a class or module
58
+ // Otherwise, it will be rb_class_of(self).
59
+ // This is done so that, should the caller decide to retain the raw_location
60
+ // instance for a while, GC mark its VALUEs, and stringify the backtrace
61
+ // later, we both:
62
+ // - retain enough information to actually produce a good name for the
63
+ // method,
64
+ // - but also don't hold onto random objects that would otherwise be GC'd
65
+ // just because they happened to be in a backtrace.
66
+ // This needs to be marked if you wish to keep the location alive
67
+ VALUE self_or_self_class;
68
+ // Raw PC pointer; not much use to callers, but saved so we can calculate the
69
+ // lineno later on.
70
+ const void *pc;
71
+ } raw_location;
72
+
73
+ // Returns the number of Ruby frames currently live on the thread; this
74
+ // can be used to judge how many times backtracie_capture_frame_for_thread()
75
+ // should be called to capture the actual frames.
76
+ BACKTRACIE_API int backtracie_frame_count_for_thread(VALUE thread);
77
+ // Fills in the raw_location struct with the details of the Ruby call stack
78
+ // frame on the given thread at the given index. The given index may or may not
79
+ // refer to a "valid" frame on the ruby stack; there are some entries on the
80
+ // Ruby stack which are not actually meaningful from a call-stack perspective.
81
+ //
82
+ // The maximum allowable value for i is
83
+ // (backtracie_frame_count_for_thread(thread) - 1). Any value higher than this
84
+ // will crash.
85
+
86
+ // If the given index DOES refer to a valid frame on the Ruby stack:
87
+ // - *loc will be filled in with details of the frame,
88
+ // - the return value will be true
89
+ // If the given index DOES NOT refer to a valid frame, then:
90
+ // - *loc will be totally untouched,
91
+ // - the return value will be false
92
+ //
93
+ // The intended usage of this API looks something like this:
94
+ //
95
+ // VALUE thread = rb_thread_current();
96
+ // int max_frame_count = backtracie_frame_count_for_thread(thread);
97
+ // raw_location *locs = xcalloc(frame_count ,sizeof(raw_location));
98
+ // int frame_counter = 0;
99
+ // for (int i = 0; i < max_frame_count; i++) {
100
+ // bool is_valid = backtracie_capture_frame_for_thread(thread, i,
101
+ // &locs[frame_counter]);
102
+ // if (is_valid) frame_counter++;
103
+ // }
104
+ BACKTRACIE_API
105
+ bool backtracie_capture_frame_for_thread(VALUE thread, int frame_index,
106
+ raw_location *loc);
107
+ // Get the "qualified method name" for the frame. This is a string that best
108
+ // describes what method is being called, intended for human interpretation.
109
+ // Writes a NULL-term'd string of at most buflen chars (including NULL
110
+ // terminator) to *buf. It returns the number of characters (NOT including NULL
111
+ // terminator) that would be required to store the full string (which might be >
112
+ // buflen, if it has been truncated). Essentially, has the same semantics as
113
+ // snprintf or strlcat.
114
+ BACKTRACIE_API
115
+ size_t backtracie_frame_name_cstr(const raw_location *loc, char *buf,
116
+ size_t buflen);
117
+ // Like backtracie_frame_name_cstr, but will allocate memory to ensure that
118
+ // there is no truncation of the method name; returns a Ruby string.
119
+ BACKTRACIE_API
120
+ VALUE backtracie_frame_name_rbstr(const raw_location *loc);
121
+ // Returns the filename of the source file for this backtrace location. Has the
122
+ // same string handling semantics as backtracie_frame_name_cstr.
123
+ //
124
+ // Pass true/false for absolute to get the full, realpath vs just the path.
125
+ //
126
+ // If loc is a cfunc, and therefore has no filename, 0 is returned (and no
127
+ // string is written to *buf)
128
+ BACKTRACIE_API
129
+ size_t backtracie_frame_filename_cstr(const raw_location *loc, bool absolute,
130
+ char *buf, size_t buflen);
131
+ // Like backtracie_frame_filename_cstr, but returns a Ruby string. Will allocate
132
+ // memory to ensure there is no truncation. Returns Qnil if there is no
133
+ // filename.
134
+ BACKTRACIE_API
135
+ VALUE backtracie_frame_filename_rbstr(const raw_location *loc, bool absolute);
136
+
137
+ // Returns the source line number of the given location, if it's a Ruby frame
138
+ // (otherwise, returns 0).
139
+ BACKTRACIE_API
140
+ int backtracie_frame_line_number(const raw_location *loc);
141
+ // Returns a string which would be like the "label" returned by
142
+ // rb_profile_frames or Thread#backtrace or similar.
143
+ BACKTRACIE_API
144
+ size_t backtracie_frame_label_cstr(const raw_location *loc, bool base,
145
+ char *buf, size_t buflen);
146
+ // Like backtracie_frame_label_cstr, but returns a ruby string.
147
+ BACKTRACIE_API
148
+ VALUE backtracie_frame_label_rbstr(const raw_location *loc, bool base);
149
+ // Returns a VALUE that can be passed into the rb_profile_frames family of
150
+ // methods
151
+ BACKTRACIE_API
152
+ VALUE backtracie_frame_for_rb_profile(const raw_location *loc);
153
+ // Marks the contained ruby objects; for use when you want to persist the
154
+ // raw_location object beyond the current call stack.
155
+ BACKTRACIE_API
156
+ void backtracie_frame_mark(const raw_location *loc);
157
+ // Like backtracie_frame_mark, but calls rb_gc_mark_movable if available.
158
+ BACKTRACIE_API
159
+ void backtracie_frame_mark_movable(const raw_location *loc);
160
+ // Updates *loc's VALUEs to point to possibly-moved locations
161
+ BACKTRACIE_API
162
+ void backtracie_frame_compact(raw_location *loc);
163
+
164
+ // This is an optional facility to create a VALUE containing an array of
165
+ // backtracie frames, which itself owns the process of marking them. When using
166
+ // this, you need only mark the wrapper, and the wrapper will take care of
167
+ // marking the individual frames. NOTE - if you keep a reference to this value
168
+ // on the stack, you _probably_ need to use RB_GC_GUARD() on it, because the
169
+ // Ruby GC cannot trace usage of the underlying pointer.
170
+ BACKTRACIE_API
171
+ VALUE backtracie_frame_wrapper_new(size_t capa);
172
+ // Returns the underying array of frames for use
173
+ BACKTRACIE_API
174
+ raw_location *backtracie_frame_wrapper_frames(VALUE wrapper);
175
+ // This returns a pointer to the len, so you can update the size of the
176
+ // contained list of frames (up to capa)
177
+ BACKTRACIE_API
178
+ int *backtracie_frame_wrapper_len(VALUE wrapper);
179
+
180
+ // ========= "Minimal" API ========
181
+ // This part of the API defines a "minimal" version of raw_location, called
182
+ // minimal_location_t. The problem this solves is that marking the iseq &
183
+ // callable method entry stored on the raw_location struct can actually mark a
184
+ // whole lot of memory, but only a small amount of those things are actually
185
+ // needed to compute the method name.
186
+
187
+ // These values define union descriminator values for minimal_location_t.
188
+ // It's tempting to make this an enum, but apparently an enum in a bitfield is
189
+ // implementation-defined behaviour.
190
+ // It would also be tempting to define these as "const uint16_t", but the
191
+ // ancient version of mingw used to build for Ruby 2.3 on Windows doesn't like
192
+ // using such a constant in a switch statement somehow (??).
193
+ // So... #define's it is.
194
+ #define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF 0u
195
+ #define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS 1u
196
+ #define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS 2u
197
+ #define BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID 0u
198
+ #define BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL 1u
199
+
200
+ typedef struct {
201
+ // 1 -> ruby frame / 0 -> cfunc frame
202
+ uint16_t is_ruby_frame : 1;
203
+ // What is in the method_qualifier field?
204
+ // 0 -> self
205
+ // 1 -> self_class
206
+ // 2 -> cme_defined_class
207
+ uint16_t method_qualifier_contents : 2;
208
+ // 0 -> cme_method_id
209
+ // 1 -> base_label
210
+ uint16_t method_name_contents : 1;
211
+ // 1 means iseq_type is populated
212
+ uint16_t has_iseq_type : 1;
213
+
214
+ // Comes from iseq->body->type; is an iseq_type enum.
215
+ // 5 bits is _way_ more than enough to store this enum.
216
+ uint16_t iseq_type : 5;
217
+
218
+ // We have six bits left over if someone can come up with a good use for them.
219
+ uint16_t reserved_bits : 6;
220
+
221
+ // Line number - we calculate this at capture time because it's fast, and it
222
+ // saves us from storing the iseq.
223
+ uint32_t line_number;
224
+
225
+ // Contains either the callable method entry ID or the iseq base label. The
226
+ // former if has_cme_method_id is set, the latter if not.
227
+ // If has_cme_method_id is set, this need not be marked, but if it is NOT set,
228
+ // then method_name.base_label needs to be marked.
229
+ union {
230
+ // Comes from callable_method_entry->called_id
231
+ ID cme_method_id;
232
+ // Comes from iseq->body->location.base_label
233
+ VALUE base_label;
234
+ } method_name;
235
+
236
+ // VALUE for the filename (abs), or Qnil
237
+ // Needs to be marked
238
+ VALUE filename;
239
+
240
+ // See method_qualifier_contents flag for possible values that can be in here.
241
+ // Needs to be marked
242
+ union {
243
+ VALUE self; // the VALUE the method was called on
244
+ VALUE self_class; // rb_class_of(the VALUE the method was called on)
245
+ VALUE cme_defined_class; // the class that the callable_method_entry is
246
+ // defined on
247
+ } method_qualifier;
248
+ } minimal_location_t;
249
+
250
+ // This is like backtracie_capture_frame_for_thread, but captures a
251
+ // minimal_location_t instead of a raw_location.
252
+ BACKTRACIE_API
253
+ bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
254
+ minimal_location_t *loc);
255
+
256
+ // This is like backtracie_frame_name_cstr, but works on a minimal_location_t
257
+ // instead of a raw_location
258
+ BACKTRACIE_API
259
+ size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
260
+ char *buf, size_t buflen);
261
+
262
+ // This is like backtracie_frame_filename_cstr, but works on a
263
+ // minimal_location_t instead of a raw_location. Note that this always returns
264
+ // the absolute filename.
265
+ BACKTRACIE_API
266
+ size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
267
+ char *buf, size_t buflen);
268
+ #endif
@@ -0,0 +1,123 @@
1
+ #include <ruby.h>
2
+ #include <stdarg.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+
6
+ #include "backtracie_private.h"
7
+ #include "strbuilder.h"
8
+
9
+ void strbuilder_init(strbuilder_t *str, char *buf, size_t bufsize) {
10
+ str->original_buf = buf;
11
+ str->original_bufsize = bufsize;
12
+ str->curr_ptr = str->original_buf;
13
+ str->attempted_size = 0;
14
+ if (str->original_bufsize > 0) {
15
+ str->original_buf[0] = '\0';
16
+ }
17
+ str->growable = false;
18
+ }
19
+
20
+ void strbuilder_init_growable(strbuilder_t *str, size_t initial_bufsize) {
21
+ str->original_bufsize = initial_bufsize;
22
+ str->original_buf = malloc(str->original_bufsize);
23
+ str->curr_ptr = str->original_buf;
24
+ str->attempted_size = 0;
25
+ if (str->original_bufsize > 0) {
26
+ str->original_buf[0] = '\0';
27
+ }
28
+ str->growable = true;
29
+ }
30
+
31
+ void strbuilder_free_growable(strbuilder_t *str) {
32
+ BACKTRACIE_ASSERT(str->growable);
33
+ free(str->original_buf);
34
+ }
35
+
36
+ static void strbuilder_grow(strbuilder_t *str) {
37
+ ptrdiff_t offset = str->curr_ptr - str->original_buf;
38
+ str->original_bufsize = str->original_bufsize * 2;
39
+ str->original_buf = realloc(str->original_buf, str->original_bufsize);
40
+ str->curr_ptr = str->original_buf + offset;
41
+ }
42
+
43
+ void strbuilder_appendf(strbuilder_t *str, const char *fmt, ...) {
44
+ va_list fmtargs;
45
+ va_start(fmtargs, fmt);
46
+
47
+ retry:;
48
+ // The size left in the buffer
49
+ size_t max_writesize =
50
+ str->original_bufsize - (str->curr_ptr - str->original_buf);
51
+ // vsnprintf returns the number of bytes it _would_ have written, not
52
+ // including the null terminator.
53
+ size_t attempted_writesize_wo_nullterm =
54
+ vsnprintf(str->curr_ptr, max_writesize, fmt, fmtargs);
55
+ if (attempted_writesize_wo_nullterm >= max_writesize) {
56
+ // Can we grow & retry?
57
+ if (str->growable) {
58
+ strbuilder_grow(str);
59
+ goto retry;
60
+ }
61
+ // If the string (including nullterm) would have exceeded the bufsize,
62
+ // point str->curr_ptr to one-past-the-end of the buffer.
63
+ // This will make subsequent calls to vsnprintf() receive zero for
64
+ // max_writesize, no further things can be appended to this buffer.
65
+ str->curr_ptr = str->original_buf + str->original_bufsize;
66
+ } else {
67
+ // If there's still room in the buffer, advance the curr_ptr.
68
+ str->curr_ptr = str->curr_ptr + attempted_writesize_wo_nullterm;
69
+ }
70
+ str->attempted_size += attempted_writesize_wo_nullterm;
71
+ va_end(fmtargs);
72
+ }
73
+
74
+ void strbuilder_append(strbuilder_t *str, const char *cat) {
75
+ retry:;
76
+ size_t max_writesize =
77
+ str->original_bufsize - (str->curr_ptr - str->original_buf);
78
+ size_t attempted_writesize_wo_nullterm =
79
+ strlcat(str->curr_ptr, cat, max_writesize);
80
+ if (attempted_writesize_wo_nullterm >= max_writesize) {
81
+ if (str->growable) {
82
+ strbuilder_grow(str);
83
+ goto retry;
84
+ }
85
+ str->curr_ptr = str->original_buf + str->original_bufsize;
86
+ } else {
87
+ str->curr_ptr = str->curr_ptr + attempted_writesize_wo_nullterm;
88
+ }
89
+ str->attempted_size += attempted_writesize_wo_nullterm;
90
+ }
91
+
92
+ void strbuilder_append_value(strbuilder_t *str, VALUE val) {
93
+ BACKTRACIE_ASSERT(RB_TYPE_P(val, T_STRING));
94
+
95
+ const char *val_ptr = RSTRING_PTR(val);
96
+ size_t val_len = RSTRING_LEN(val);
97
+
98
+ retry:;
99
+ size_t max_writesize =
100
+ str->original_bufsize - (str->curr_ptr - str->original_buf);
101
+ size_t chars_to_copy = val_len;
102
+ if (chars_to_copy + 1 > max_writesize) {
103
+ if (str->growable) {
104
+ strbuilder_grow(str);
105
+ goto retry;
106
+ }
107
+ chars_to_copy = max_writesize - 1; // leave room for NULL terminator.
108
+ }
109
+ memcpy(str->curr_ptr, val_ptr, chars_to_copy);
110
+ str->curr_ptr[chars_to_copy] = '\0';
111
+ str->attempted_size += val_len;
112
+ if (val_len + 1 > max_writesize) {
113
+ str->curr_ptr = str->original_buf + str->original_bufsize;
114
+ } else {
115
+ str->curr_ptr += val_len;
116
+ }
117
+
118
+ RB_GC_GUARD(val);
119
+ }
120
+
121
+ VALUE strbuilder_to_value(strbuilder_t *str) {
122
+ return rb_str_new2(str->original_buf);
123
+ }
@@ -0,0 +1,23 @@
1
+ #ifndef STRBUILDER_H
2
+ #define STRBUILDER_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+ #include <stddef.h>
7
+
8
+ typedef struct {
9
+ char *original_buf;
10
+ char *curr_ptr;
11
+ size_t original_bufsize;
12
+ size_t attempted_size;
13
+ bool growable;
14
+ } strbuilder_t;
15
+
16
+ void strbuilder_append(strbuilder_t *str, const char *cat);
17
+ void strbuilder_appendf(strbuilder_t *str, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
18
+ void strbuilder_append_value(strbuilder_t *str, VALUE val);
19
+ VALUE strbuilder_to_value(strbuilder_t *str);
20
+ void strbuilder_init(strbuilder_t *str, char *buf, size_t bufsize);
21
+ void strbuilder_init_growable(strbuilder_t *str, size_t initial_bufsize);
22
+ void strbuilder_free_growable(strbuilder_t *str);
23
+ #endif
@@ -27,16 +27,21 @@ module Backtracie
27
27
  attr_accessor :lineno
28
28
  attr_accessor :path
29
29
  attr_accessor :qualified_method_name
30
+ attr_accessor :path_is_synthetic
30
31
 
31
32
  # Note: The order of arguments is hardcoded in the native extension in the `new_location` function --
32
33
  # keep them in sync
33
- def initialize(absolute_path, base_label, label, lineno, path, qualified_method_name, debug)
34
+ def initialize(
35
+ absolute_path, base_label, label, lineno, path, qualified_method_name,
36
+ path_is_synthetic, debug
37
+ )
34
38
  @absolute_path = absolute_path
35
39
  @base_label = base_label
36
40
  @label = label
37
41
  @lineno = lineno
38
42
  @path = path
39
43
  @qualified_method_name = qualified_method_name
44
+ @path_is_synthetic = path_is_synthetic
40
45
  @debug = debug
41
46
 
42
47
  freeze
@@ -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.3.0"
22
+ VERSION = "1.1.0"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backtracie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.1.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-09-16 00:00:00.000000000 Z
11
+ date: 2023-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: debase-ruby_core_source
@@ -45,9 +45,13 @@ files:
45
45
  - README.adoc
46
46
  - backtracie.gemspec
47
47
  - ext/backtracie_native_extension/backtracie.c
48
+ - ext/backtracie_native_extension/backtracie_frames.c
49
+ - ext/backtracie_native_extension/backtracie_private.h
50
+ - ext/backtracie_native_extension/c_test_helpers.c
48
51
  - ext/backtracie_native_extension/extconf.rb
49
- - ext/backtracie_native_extension/ruby_shards.c
50
- - ext/backtracie_native_extension/ruby_shards.h
52
+ - ext/backtracie_native_extension/public/backtracie.h
53
+ - ext/backtracie_native_extension/strbuilder.c
54
+ - ext/backtracie_native_extension/strbuilder.h
51
55
  - lib/backtracie.rb
52
56
  - lib/backtracie/location.rb
53
57
  - lib/backtracie/version.rb
@@ -71,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
75
  - !ruby/object:Gem::Version
72
76
  version: '0'
73
77
  requirements: []
74
- rubygems_version: 3.1.4
78
+ rubygems_version: 3.4.1
75
79
  signing_key:
76
80
  specification_version: 4
77
81
  summary: Ruby gem for beautiful backtraces