backtracie 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,68 +18,62 @@
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
- "\n#{"-" * 80}\nSorry! This gem is unsupported on #{RUBY_ENGINE}. Since it relies on a lot of guts of MRI Ruby, " \
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" \
25
- "Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{"-" * 80}"
25
+ "Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{'-' * 80}"
26
26
  end
27
27
 
28
- require "mkmf"
28
+ require 'mkmf'
29
29
 
30
30
  # This warning gets really annoying when we include the Ruby mjit header file,
31
31
  # let's omit it
32
- $CFLAGS << " " << "-Wno-unused-function"
32
+ $CFLAGS << ' ' << '-Wno-unused-function'
33
33
 
34
34
  # Really dislike the "define everything at the beginning of the function" thing, sorry!
35
- $CFLAGS << " " << "-Wno-declaration-after-statement"
35
+ $CFLAGS << ' ' << '-Wno-declaration-after-statement'
36
36
 
37
37
  # If we forget to include a Ruby header, the function call may still appear to work, but then
38
38
  # cause a segfault later. Let's ensure that never happens.
39
- $CFLAGS << " " << "-Werror-implicit-function-declaration"
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
- 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
52
+ if RUBY_VERSION < '2.5'
53
+ $CFLAGS << ' ' << '-DPRE_EXECUTION_CONTEXT' # Flag that there's no execution context, we need to use threads instead
54
+ $CFLAGS << ' ' << '-DPRE_LOCATION_PATHOBJ'
55
+ $CFLAGS << ' ' << '-Wno-attributes' # Silence a few warnings that we can't do anything about
64
56
  end
65
57
 
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
58
+ if RUBY_VERSION < '2.4'
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
- if RUBY_VERSION < "2.6"
66
+ if RUBY_VERSION < '2.6'
73
67
  # Use the debase-ruby_core_source gem to get access to Ruby internal structures (no MJIT header -- the preferred
74
68
  # option -- is available for these older Rubies)
75
69
 
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"
70
+ require 'debase/ruby_core_source'
71
+ dir_config('ruby') # allow user to pass in non-standard core include directory
72
+ unless Debase::RubyCoreSource.create_makefile_with_core(
73
+ proc { ['vm_core.h', 'method.h', 'iseq.h', 'regenc.h'].map { |it| have_header(it) }.uniq == [true] },
74
+ 'backtracie_native_extension'
81
75
  )
82
- raise "Error during native gem setup -- `Debase::RubyCoreSource.create_makefile_with_core` failed"
76
+ raise 'Error during native gem setup -- `Debase::RubyCoreSource.create_makefile_with_core` failed'
83
77
  end
84
78
 
85
79
  else
@@ -90,14 +84,14 @@ else
90
84
  # containing the exact file, so that it can be used in a #include in the C code.
91
85
  header_contents =
92
86
  File.read($extconf_h)
93
- .sub("#endif",
94
- <<~EXTCONF_H.strip
95
- #define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
87
+ .sub('#endif',
88
+ <<~EXTCONF_H.strip
89
+ #define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
96
90
 
97
- #endif
98
- EXTCONF_H
99
- )
100
- File.open($extconf_h, "w") { |file| file.puts(header_contents) }
91
+ #endif
92
+ EXTCONF_H
93
+ )
94
+ File.open($extconf_h, 'w') { |file| file.puts(header_contents) }
101
95
 
102
- create_makefile "backtracie_native_extension"
96
+ create_makefile 'backtracie_native_extension'
103
97
  end
@@ -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, ...);
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.0.0"
23
23
  end