racer-rb 0.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +9 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +23 -0
- data/ext/racer/extconf.rb +14 -0
- data/ext/racer/racer.cc +999 -0
- data/ext/racer/racer.hh +37 -0
- data/ext/racer/tiny_queue.cc +103 -0
- data/ext/racer/tiny_queue.hh +39 -0
- data/ext/racer/traces.cc +70 -0
- data/ext/racer/traces.hh +95 -0
- data/ext/racer/worker.cc +188 -0
- data/ext/racer/worker.hh +15 -0
- data/lib/racer/agent.rb +250 -0
- data/lib/racer/collectors/rbs_collector.rb +645 -0
- data/lib/racer/minitest.rb +20 -0
- data/lib/racer/minitest_plugin.rb +35 -0
- data/lib/racer/rails/railtie.rb +38 -0
- data/lib/racer/rb.rb +2 -0
- data/lib/racer/rspec.rb +19 -0
- data/lib/racer/rspec_plugin.rb +28 -0
- data/lib/racer/trace.rb +121 -0
- data/lib/racer/version.rb +5 -0
- data/lib/racer.rb +40 -0
- data/sig/racer.rbs +4 -0
- metadata +87 -0
data/ext/racer/racer.cc
ADDED
|
@@ -0,0 +1,999 @@
|
|
|
1
|
+
#include "racer.hh"
|
|
2
|
+
#include <sys/socket.h>
|
|
3
|
+
#include <sys/un.h>
|
|
4
|
+
#include <unordered_map>
|
|
5
|
+
#include <unordered_set>
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include <memory>
|
|
8
|
+
|
|
9
|
+
#define DEBUG 0
|
|
10
|
+
#define debug_warn(fmt, ...) \
|
|
11
|
+
do { if(DEBUG) rb_warn(fmt, __VA_ARGS__); } while(0)
|
|
12
|
+
|
|
13
|
+
static VALUE tpCall = Qnil;
|
|
14
|
+
static pthread_t pthread;
|
|
15
|
+
static tiny_queue_t *tiny_queue;
|
|
16
|
+
static int flushed = 0;
|
|
17
|
+
static int socketFd = 0;
|
|
18
|
+
|
|
19
|
+
static VALUE Racer = Qnil;
|
|
20
|
+
static VALUE pathMatcher = Qnil;
|
|
21
|
+
static long maxGenericDepth = 1;
|
|
22
|
+
|
|
23
|
+
static ID reqParam, optParam, restParam, keyreqParam, keyParam, keyrestParam, nokeyParam, blockParam, anonRest, anonKeyrest, anonBlock,
|
|
24
|
+
method_defined, public_method_defined, private_method_defined,
|
|
25
|
+
callerLocationsId, pathId = -1;
|
|
26
|
+
|
|
27
|
+
static std::unordered_map<unsigned long, std::vector<ReturnTrace*>> call_stacks;
|
|
28
|
+
|
|
29
|
+
// TODO: Think about if this can break with GCs. Currently we are not marking those objects so the GC
|
|
30
|
+
// cannot know if we hold a reference to them. Also with GC compaction the object behind a pointer
|
|
31
|
+
// could move (as far as I understand, CRuby prevents this for C-Extensions).
|
|
32
|
+
static std::unordered_map<VALUE, std::shared_ptr<Constant>> constant_map = {};
|
|
33
|
+
static std::vector<VALUE> constant_updates {};
|
|
34
|
+
|
|
35
|
+
static std::unordered_map<VALUE, bool> matched_paths = {};
|
|
36
|
+
|
|
37
|
+
static ClassType
|
|
38
|
+
class_type_by_constant(VALUE constant) {
|
|
39
|
+
auto type = rb_type(constant);
|
|
40
|
+
|
|
41
|
+
if(type == T_CLASS) {
|
|
42
|
+
return CLASS;
|
|
43
|
+
} else
|
|
44
|
+
if(type == T_MODULE) {
|
|
45
|
+
return MODULE;
|
|
46
|
+
} else {
|
|
47
|
+
rb_warn("UNEXPECTED method owner type %d", type);
|
|
48
|
+
return MODULE;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static std::shared_ptr<Constant>
|
|
53
|
+
explore_constant(VALUE ruby_constant, std::unordered_set<VALUE> *visited_constants);
|
|
54
|
+
|
|
55
|
+
static VALUE explore_constant_namespace(VALUE constant_name) {
|
|
56
|
+
auto current_space = rb_cObject;
|
|
57
|
+
auto class_name_str = strdup(StringValueCStr(constant_name));
|
|
58
|
+
long constant_path_size = 0;
|
|
59
|
+
char* occurence = nullptr;
|
|
60
|
+
do {
|
|
61
|
+
occurence = strstr(class_name_str, "::");
|
|
62
|
+
|
|
63
|
+
if(!occurence) break;
|
|
64
|
+
|
|
65
|
+
occurence[0] = '\0';
|
|
66
|
+
current_space = rb_const_get_at(current_space, rb_intern(class_name_str));
|
|
67
|
+
|
|
68
|
+
explore_constant(current_space, nullptr);
|
|
69
|
+
|
|
70
|
+
class_name_str = occurence + 2;
|
|
71
|
+
} while(occurence != nullptr);
|
|
72
|
+
|
|
73
|
+
return current_space;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static std::shared_ptr<Constant>
|
|
77
|
+
explore_constant(VALUE ruby_constant, std::unordered_set<VALUE> *visited_constants = nullptr) {
|
|
78
|
+
auto existing_constant = constant_map.find(ruby_constant);
|
|
79
|
+
if(existing_constant != constant_map.end()) {
|
|
80
|
+
return (*existing_constant).second;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
auto constant_type = class_type_by_constant(ruby_constant);
|
|
84
|
+
|
|
85
|
+
VALUE ruby_constant_name = rb_mod_name(ruby_constant);
|
|
86
|
+
bool anonymous = false;
|
|
87
|
+
if(RB_NIL_P(ruby_constant_name)) {
|
|
88
|
+
anonymous = true;
|
|
89
|
+
VALUE klass = ruby_constant;
|
|
90
|
+
if(RB_TYPE_P(klass, T_CLASS)) {
|
|
91
|
+
do {
|
|
92
|
+
klass = rb_class_superclass(klass);
|
|
93
|
+
if(!RB_TEST(klass)) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ruby_constant_name = rb_class_path_cached(klass);
|
|
98
|
+
} while(RB_NIL_P(ruby_constant_name));
|
|
99
|
+
|
|
100
|
+
ruby_constant_name = rb_str_plus(ruby_constant_name, rb_fix2str(INT2FIX(ruby_constant), 10));
|
|
101
|
+
} else {
|
|
102
|
+
// Module
|
|
103
|
+
ruby_constant_name = rb_str_append(rb_str_new_cstr("Module"), rb_fix2str(INT2FIX(ruby_constant), 10));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
auto constant_name = strdup(StringValueCStr(ruby_constant_name));
|
|
108
|
+
|
|
109
|
+
Constant constant_obj = { constant_name, anonymous, constant_type };
|
|
110
|
+
std::shared_ptr<Constant> constant = std::make_shared<Constant>(constant_obj);
|
|
111
|
+
constant_map.emplace(ruby_constant, constant);
|
|
112
|
+
|
|
113
|
+
if(!anonymous) {
|
|
114
|
+
// Explore namespace
|
|
115
|
+
auto result = ::rb_rescue2(explore_constant_namespace, ruby_constant_name, nullptr, 0, rb_eNameError, (VALUE) 0);
|
|
116
|
+
// If explore_constant_namespace raises the namespace includes an anonymous class.
|
|
117
|
+
// We cannot determine a good name for this so we just use Class/Module with the object id.
|
|
118
|
+
if(RB_NIL_P(result)) {
|
|
119
|
+
constant->anonymous = true;
|
|
120
|
+
VALUE constant_name;
|
|
121
|
+
if(constant_type == CLASS) {
|
|
122
|
+
constant_name = rb_str_new_cstr("Class");
|
|
123
|
+
} else {
|
|
124
|
+
constant_name = rb_str_new_cstr("Module");
|
|
125
|
+
}
|
|
126
|
+
constant_name = rb_str_append(constant_name, rb_fix2str(INT2FIX(ruby_constant), 10));
|
|
127
|
+
|
|
128
|
+
constant->name = strdup(StringValueCStr(constant_name));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
auto ancestors = rb_mod_ancestors(ruby_constant);
|
|
133
|
+
|
|
134
|
+
// Object is the superclass of all classes (except BasicObject)
|
|
135
|
+
VALUE stop_object = rb_cObject;
|
|
136
|
+
if(stop_object == ruby_constant) {
|
|
137
|
+
// The supplied constant is object
|
|
138
|
+
stop_object = rb_mKernel;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
auto ancestors_pointer = RARRAY_CONST_PTR(ancestors);
|
|
142
|
+
bool prepended = true;
|
|
143
|
+
std::unordered_set<VALUE> inner_constants = {};
|
|
144
|
+
for(auto i = 0; i < rb_array_len(ancestors); ++i) {
|
|
145
|
+
auto ancestor = ancestors_pointer[i];
|
|
146
|
+
|
|
147
|
+
if(ancestor == ruby_constant) {
|
|
148
|
+
// barrier from prepended to included/superclass
|
|
149
|
+
prepended = false;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if(ancestor == stop_object) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if(constant_type == CLASS && rb_type_p(ancestor, T_CLASS)) {
|
|
158
|
+
if(prepended) {
|
|
159
|
+
auto ancestor_inspect = rb_inspect(ancestor);
|
|
160
|
+
rb_warn("Class %s in ancestors of %s before self (prepended class?)", StringValueCStr(ancestor_inspect), constant_name);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
auto ancestor_constant = explore_constant(ancestor);
|
|
165
|
+
constant->superclass = ancestor_constant->name;
|
|
166
|
+
// After the superclass there will be other superclasses or modules included in the super classes
|
|
167
|
+
// which are not directly included into constant. This means when reaching the superclass we reached
|
|
168
|
+
// the end of constant's direct ancestor chain
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Constant is a module
|
|
173
|
+
if(inner_constants.find(ancestor) == inner_constants.end()) {
|
|
174
|
+
auto ancestor_constant = explore_constant(ancestor, &inner_constants);
|
|
175
|
+
if(visited_constants) visited_constants->emplace(ancestor);
|
|
176
|
+
|
|
177
|
+
if(prepended) {
|
|
178
|
+
constant->prepended_modules.push_back(ancestor_constant->name);
|
|
179
|
+
} else {
|
|
180
|
+
constant->included_modules.push_back(ancestor_constant->name);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
auto singleton_class = rb_singleton_class(ruby_constant);
|
|
186
|
+
auto singleton_class_ancestors = rb_mod_ancestors(singleton_class);
|
|
187
|
+
auto singleton_class_ancestors_pointer = RARRAY_CONST_PTR(singleton_class_ancestors);
|
|
188
|
+
|
|
189
|
+
auto object_singleton_class = rb_singleton_class(rb_cObject);
|
|
190
|
+
|
|
191
|
+
// Note: In pricinple it's possible to prepend a module to a singleton class. Let's see
|
|
192
|
+
// if we need that in the future. As far as I know it's not possible to type this with RBS anyway.
|
|
193
|
+
for(auto i = 0; i < rb_array_len(singleton_class_ancestors); ++i) {
|
|
194
|
+
auto ancestor = singleton_class_ancestors_pointer[i];
|
|
195
|
+
|
|
196
|
+
if(ancestor == singleton_class) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if(ancestor == object_singleton_class || ancestor == rb_cModule) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if(RB_TYPE_P(ancestor, T_MODULE)) {
|
|
205
|
+
// Constant is a module
|
|
206
|
+
if(inner_constants.find(ancestor) == inner_constants.end()) {
|
|
207
|
+
auto ancestor_constant = explore_constant(ancestor, &inner_constants);
|
|
208
|
+
if(visited_constants) visited_constants->emplace(ancestor);
|
|
209
|
+
|
|
210
|
+
constant->extended_modules.push_back(ancestor_constant->name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if(visited_constants) {
|
|
216
|
+
for(auto elem : inner_constants) {
|
|
217
|
+
visited_constants->emplace(elem);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
constant_updates.push_back(ruby_constant);
|
|
222
|
+
return constant;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static ConstantInstance
|
|
226
|
+
class_to_constant_instance(VALUE klass, unsigned char generic_argument_count = 0, std::vector<ConstantInstance>* generic_arguments = nullptr) {
|
|
227
|
+
auto constant = explore_constant(klass);
|
|
228
|
+
|
|
229
|
+
return { constant->name, generic_argument_count, generic_arguments };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static ConstantInstance
|
|
233
|
+
value_to_constant_instance(VALUE value, int depth);
|
|
234
|
+
|
|
235
|
+
static int
|
|
236
|
+
hash_to_keys_and_values(VALUE key, VALUE value, VALUE ary) {
|
|
237
|
+
rb_ary_push(ary, key);
|
|
238
|
+
rb_ary_push(ary, value);
|
|
239
|
+
return ST_CONTINUE;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
std::pair<unsigned char, std::vector<ConstantInstance>*>
|
|
243
|
+
generic_arguments_by_value(VALUE value, VALUE klass, int depth = 0) {
|
|
244
|
+
if(depth == maxGenericDepth) return { 0, nullptr };
|
|
245
|
+
|
|
246
|
+
if (klass == rb_cArray) {
|
|
247
|
+
auto length = rb_array_len(value);
|
|
248
|
+
if(length > 0) {
|
|
249
|
+
// We need to use a set and a vector to preserve order of the union types
|
|
250
|
+
std::unordered_set<VALUE> types = {};
|
|
251
|
+
std::vector<ConstantInstance> types_vec = {};
|
|
252
|
+
auto ary_ptr = RARRAY_CONST_PTR(value);
|
|
253
|
+
// This is O(n) and thus could be pretty slow
|
|
254
|
+
for(auto j = 0; j < rb_array_len(value); ++j) {
|
|
255
|
+
auto item = ary_ptr[j];
|
|
256
|
+
|
|
257
|
+
auto constant = value_to_constant_instance(item, depth + 1);
|
|
258
|
+
auto klass = rb_obj_class(item);
|
|
259
|
+
if(constant.generic_argument_count == 0 && !constant.singleton) {
|
|
260
|
+
auto result = types.insert(klass);
|
|
261
|
+
if(result.second) {
|
|
262
|
+
types_vec.push_back(constant);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
types_vec.push_back(constant);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
auto generic_arguments = new std::vector<ConstantInstance>[1];
|
|
270
|
+
generic_arguments[0] = types_vec;
|
|
271
|
+
return { 1, generic_arguments };
|
|
272
|
+
}
|
|
273
|
+
} else
|
|
274
|
+
if(klass == rb_cHash) {
|
|
275
|
+
// RACER-TODO: I think we can optimize this, if the parameter is a keyword argument rest because
|
|
276
|
+
// keys of those must be symbols, right?
|
|
277
|
+
auto hash_size = RHASH_SIZE(value);
|
|
278
|
+
if(hash_size > 0) {
|
|
279
|
+
|
|
280
|
+
std::unordered_set<VALUE> key_types = {};
|
|
281
|
+
std::vector<ConstantInstance> key_vec = {};
|
|
282
|
+
std::unordered_set<VALUE> value_types = {};
|
|
283
|
+
std::vector<ConstantInstance> value_vec = {};
|
|
284
|
+
|
|
285
|
+
VALUE keys_and_values = rb_ary_new_capa(hash_size * 2);
|
|
286
|
+
rb_hash_foreach(value, hash_to_keys_and_values, keys_and_values);
|
|
287
|
+
auto ary_ptr = RARRAY_CONST_PTR(keys_and_values);
|
|
288
|
+
|
|
289
|
+
// This is O(2n) and thus could be pretty slow
|
|
290
|
+
for(auto j = 0; j < hash_size; ++j) {
|
|
291
|
+
auto key = ary_ptr[j * 2];
|
|
292
|
+
auto key_constant = value_to_constant_instance(key, depth + 1);
|
|
293
|
+
auto key_type = rb_obj_class(key);
|
|
294
|
+
if(key_constant.generic_argument_count == 0 && !key_constant.singleton) {
|
|
295
|
+
auto key_result = key_types.insert(key_type);
|
|
296
|
+
if(key_result.second) {
|
|
297
|
+
key_vec.push_back(key_constant);
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
key_vec.push_back(key_constant);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
auto value = ary_ptr[j * 2 + 1];
|
|
305
|
+
auto value_constant = value_to_constant_instance(value, depth + 1);
|
|
306
|
+
auto value_type = rb_obj_class(value);
|
|
307
|
+
if(value_constant.generic_argument_count == 0 && !value_constant.singleton) {
|
|
308
|
+
auto value_result = value_types.insert(value_type);
|
|
309
|
+
if(value_result.second) {
|
|
310
|
+
value_vec.push_back(value_constant);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
value_vec.push_back(value_constant);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
auto generic_arguments = new std::vector<ConstantInstance>[2];
|
|
318
|
+
generic_arguments[0] = key_vec;
|
|
319
|
+
generic_arguments[1] = value_vec;
|
|
320
|
+
|
|
321
|
+
return { 2, generic_arguments };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { 0, nullptr };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
static ConstantInstance
|
|
329
|
+
value_to_constant_instance(VALUE value, int generic_depth = 0) {
|
|
330
|
+
auto klass = rb_obj_class(value);
|
|
331
|
+
auto singleton = false;
|
|
332
|
+
|
|
333
|
+
unsigned char generic_argument_count = 0;
|
|
334
|
+
std::vector<ConstantInstance>* generic_arguments = nullptr;
|
|
335
|
+
if(klass == rb_cClass || klass == rb_cModule) {
|
|
336
|
+
klass = value;
|
|
337
|
+
singleton = true;
|
|
338
|
+
} else {
|
|
339
|
+
auto generics = generic_arguments_by_value(value, klass, generic_depth);
|
|
340
|
+
generic_argument_count = generics.first;
|
|
341
|
+
generic_arguments = generics.second;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
auto constant = class_to_constant_instance(klass, generic_argument_count, generic_arguments);
|
|
345
|
+
constant.singleton = singleton;
|
|
346
|
+
|
|
347
|
+
return constant;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
static bool
|
|
351
|
+
process_event_check_path(VALUE path)
|
|
352
|
+
{
|
|
353
|
+
// If the path is not a string we just assume that we should trace that
|
|
354
|
+
if(!RB_TYPE_P(path, T_STRING)) return true;
|
|
355
|
+
|
|
356
|
+
auto path_hash = ST2FIX(rb_str_hash(path));
|
|
357
|
+
auto matched_path = matched_paths.find(path_hash);
|
|
358
|
+
if(matched_path != matched_paths.end()) {
|
|
359
|
+
return (*matched_path).second;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
auto range = RSTRING_LEN(path);
|
|
363
|
+
|
|
364
|
+
struct reg_onig_search_args args = {
|
|
365
|
+
.pos = 0,
|
|
366
|
+
.range = range,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
bool result = true;
|
|
370
|
+
if(rb_reg_onig_match(pathMatcher, path, reg_onig_search, &args, NULL) == ONIG_MISMATCH) {
|
|
371
|
+
result = false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
matched_paths.emplace(path_hash, result);
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
static bool
|
|
379
|
+
should_process_event(rb_trace_arg_t *trace_arg)
|
|
380
|
+
{
|
|
381
|
+
if(RB_NIL_P(pathMatcher)) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
auto path = rb_tracearg_path(trace_arg);
|
|
386
|
+
if(process_event_check_path(path)) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
VALUE frames[] = { Qnil, Qnil };
|
|
391
|
+
// There is currently a bug in rb_profile_frames that
|
|
392
|
+
// causes the start argument to effectively always
|
|
393
|
+
// act as if it were 0, so we need to also get the top
|
|
394
|
+
// frame. (https://bugs.ruby-lang.org/issues/14607)
|
|
395
|
+
// This seems to have been fixed with Ruby 3.4
|
|
396
|
+
auto frames_found = rb_profile_frames(0, 2, frames, nullptr);
|
|
397
|
+
VALUE caller_path = rb_profile_frame_path(frames[1]);
|
|
398
|
+
|
|
399
|
+
if(caller_path == Qnil) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if(process_event_check_path(caller_path)) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static void process_block_call_event(rb_trace_arg_t*, ReturnTrace*);
|
|
411
|
+
static void process_block_return_event(rb_trace_arg_t*, ReturnTrace*);
|
|
412
|
+
|
|
413
|
+
static void
|
|
414
|
+
process_block_tracepoint(VALUE trace_point, void *data)
|
|
415
|
+
{
|
|
416
|
+
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(trace_point);
|
|
417
|
+
auto *trace = (ReturnTrace*) data;
|
|
418
|
+
|
|
419
|
+
switch(rb_tracearg_event_flag(trace_arg)) {
|
|
420
|
+
case RUBY_EVENT_B_CALL:
|
|
421
|
+
process_block_call_event(trace_arg, trace);
|
|
422
|
+
break;
|
|
423
|
+
case RUBY_EVENT_B_RETURN:
|
|
424
|
+
process_block_return_event(trace_arg, trace);
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Setting the target of the tracepoint may raise if the target's ISeq cannot be determined.
|
|
430
|
+
// This might happen if the proc object was defined using C (for example in Ruby core).
|
|
431
|
+
static VALUE
|
|
432
|
+
set_tracepoint_target_which_may_raise(VALUE hash) {
|
|
433
|
+
auto tp = rb_hash_delete(hash, rb_id2sym(rb_intern("tp")));
|
|
434
|
+
rb_funcallv_kw(tp, rb_intern("enable"), 1, &hash, RB_PASS_KEYWORDS);
|
|
435
|
+
return Qtrue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
static void
|
|
439
|
+
assign_parameters(ReturnTrace* trace, rb_trace_arg_t* trace_arg) {
|
|
440
|
+
VALUE parameters = rb_tracearg_parameters(trace_arg); // We may be able to cache this for each method? or access the parsed stuff idk
|
|
441
|
+
// auto param_inspect = rb_inspect(parameters);
|
|
442
|
+
// rb_warn("assiging parameters: %s", StringValueCStr(param_inspect));
|
|
443
|
+
auto total_params_size = rb_array_len(parameters);
|
|
444
|
+
trace->params_size = 0;
|
|
445
|
+
// Array of parameters objects. The array might be larger than the number of parameters we emit.
|
|
446
|
+
trace->params = new Parameter[total_params_size];
|
|
447
|
+
VALUE binding = rb_tracearg_binding(trace_arg);
|
|
448
|
+
|
|
449
|
+
bool has_block = false;
|
|
450
|
+
|
|
451
|
+
for (long i = 0; i < total_params_size; ++i)
|
|
452
|
+
{
|
|
453
|
+
VALUE param = rb_ary_entry(parameters, i);
|
|
454
|
+
VALUE name = rb_ary_entry(param, 1);
|
|
455
|
+
|
|
456
|
+
ID param_type = rb_sym2id(rb_ary_entry(param, 0));
|
|
457
|
+
|
|
458
|
+
if (RB_TEST(name))
|
|
459
|
+
{
|
|
460
|
+
auto param_name_id = rb_sym2id(name);
|
|
461
|
+
char *param_name = strdup(rb_id2name(SYM2ID(name)));
|
|
462
|
+
VALUE param_class;
|
|
463
|
+
|
|
464
|
+
if(param_type == blockParam) {
|
|
465
|
+
BlockParameter block_param = {};
|
|
466
|
+
if(param_name_id != anonBlock) {
|
|
467
|
+
block_param.name = param_name;
|
|
468
|
+
auto block = rb_funcall(binding, rb_intern("local_variable_get"), 1, name);
|
|
469
|
+
|
|
470
|
+
if(!RB_NIL_P(block)) {
|
|
471
|
+
auto tp = rb_tracepoint_new(Qnil, RUBY_EVENT_B_CALL | RUBY_EVENT_B_RETURN, process_block_tracepoint, trace);
|
|
472
|
+
auto hash = rb_hash_new_capa(2);
|
|
473
|
+
rb_hash_aset(hash, rb_id2sym(rb_intern("tp")), tp);
|
|
474
|
+
rb_hash_aset(hash, rb_id2sym(rb_intern("target")), block);
|
|
475
|
+
auto result = ::rb_rescue2(set_tracepoint_target_which_may_raise, hash, nullptr, 0, rb_eArgError, (VALUE) 0);
|
|
476
|
+
if(!RB_NIL_P(result)) {
|
|
477
|
+
block_param.tracepoint_id = tp;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
trace->block_param = block_param;
|
|
483
|
+
|
|
484
|
+
has_block = true;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
ConstantInstance constant;
|
|
489
|
+
|
|
490
|
+
if(param_name_id == anonRest) {
|
|
491
|
+
constant = class_to_constant_instance(rb_cArray);
|
|
492
|
+
} else
|
|
493
|
+
if(param_name_id == anonKeyrest) {
|
|
494
|
+
constant = class_to_constant_instance(rb_cHash);
|
|
495
|
+
} else {
|
|
496
|
+
VALUE value = rb_funcall(binding, rb_intern("local_variable_get"), 1, name);
|
|
497
|
+
constant = value_to_constant_instance(value);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
ParamType type;
|
|
501
|
+
if(param_type == reqParam) {
|
|
502
|
+
type = REQUIRED;
|
|
503
|
+
} else
|
|
504
|
+
if(param_type == optParam) {
|
|
505
|
+
type = OPTIONAL;
|
|
506
|
+
} else
|
|
507
|
+
if(param_type == restParam) {
|
|
508
|
+
type = REST;
|
|
509
|
+
} else
|
|
510
|
+
if(param_type == keyreqParam) {
|
|
511
|
+
type = KEYWORD_REQUIRED;
|
|
512
|
+
} else
|
|
513
|
+
if(param_type == keyParam) {
|
|
514
|
+
type = KEYWORD_OPTIONAL;
|
|
515
|
+
} else
|
|
516
|
+
if(param_type == keyrestParam) {
|
|
517
|
+
type = KEYWORD_REST;
|
|
518
|
+
} else {
|
|
519
|
+
rb_warn("Unknown parameter type %s\n", rb_id2name(param_type));
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
trace->params[trace->params_size] = { param_name, constant, type };
|
|
524
|
+
trace->params_size++;
|
|
525
|
+
} else {
|
|
526
|
+
if(param_type == nokeyParam) {
|
|
527
|
+
// noKey means **nil which cannot be typed with RBS i think, so we ignore it for now
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if(param_type == reqParam) {
|
|
532
|
+
trace->params[trace->params_size] = { nullptr, class_to_constant_instance(rb_cArray), REQUIRED };
|
|
533
|
+
trace->params_size++;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
auto inspected_params = rb_inspect(parameters);
|
|
538
|
+
rb_warn("Unexpected: Parameter has no name for method %s, parameters: %s", trace->method_name, StringValueCStr(inspected_params));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if(!has_block) {
|
|
543
|
+
if(rb_funcall(binding, rb_intern("block_given?"), 0) == Qtrue) {
|
|
544
|
+
trace->block_param = BlockParameter { };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
static void
|
|
550
|
+
process_call_event(rb_trace_arg_t *trace_arg)
|
|
551
|
+
{
|
|
552
|
+
ReturnTrace *trace = new ReturnTrace;
|
|
553
|
+
trace->rescued = false;
|
|
554
|
+
|
|
555
|
+
auto method_id = rb_tracearg_method_id(trace_arg);
|
|
556
|
+
trace->method_name = strdup(rb_id2name(SYM2ID(method_id)));
|
|
557
|
+
trace->method_kind = INSTANCE;
|
|
558
|
+
|
|
559
|
+
VALUE defined_class = rb_tracearg_defined_class(trace_arg);
|
|
560
|
+
auto original_defined_class = defined_class;
|
|
561
|
+
if(RB_FL_TEST_RAW(defined_class, FL_SINGLETON)) {
|
|
562
|
+
VALUE tmp_defined_class = rb_class_attached_object(defined_class);
|
|
563
|
+
auto type = rb_type(tmp_defined_class);
|
|
564
|
+
if (type == T_MODULE || type == T_CLASS) {
|
|
565
|
+
defined_class = tmp_defined_class;
|
|
566
|
+
} else {
|
|
567
|
+
// TODO: Check if these cases can still happen or if we can safe the check before
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// If the method isn't defined in self (say in a module or superclass) we want to add it to self instead.
|
|
572
|
+
// This is a decision that might be revised. It's optimized for call site devexp but is bad for the callee.
|
|
573
|
+
auto self = rb_tracearg_self(trace_arg);
|
|
574
|
+
auto self_type = rb_type(self);
|
|
575
|
+
if(self_type == T_CLASS || self_type == T_MODULE) {
|
|
576
|
+
trace->method_kind = SINGLETON;
|
|
577
|
+
} else {
|
|
578
|
+
trace->method_kind = INSTANCE;
|
|
579
|
+
self = rb_obj_class(self);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
if(self != defined_class) {
|
|
584
|
+
explore_constant(self);
|
|
585
|
+
|
|
586
|
+
auto owner = self;
|
|
587
|
+
if(trace->method_kind == SINGLETON) {
|
|
588
|
+
owner = rb_singleton_class(self);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Only change the method owner, if self does not implement the method themselves.
|
|
592
|
+
if(rb_funcall(owner, method_defined, 2, method_id, Qfalse) == Qfalse) {
|
|
593
|
+
// method_defined? documentation: "Public and protected methods are matched"
|
|
594
|
+
// so we need to check for private methods seperately
|
|
595
|
+
if(rb_funcall(owner, private_method_defined, 2, method_id, Qfalse) == Qfalse) {
|
|
596
|
+
trace->method_callee = class_to_constant_instance(self);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
trace->method_owner = class_to_constant_instance(defined_class);
|
|
603
|
+
|
|
604
|
+
long fiber_id = rb_fiber_current();
|
|
605
|
+
auto stack_pair = call_stacks.find(fiber_id);
|
|
606
|
+
if(stack_pair != call_stacks.end()) {
|
|
607
|
+
auto stack = (*stack_pair).second;
|
|
608
|
+
if(!stack.empty()) {
|
|
609
|
+
auto previous_trace = (*stack_pair).second.back();
|
|
610
|
+
// Attempt to detect retries of a method using the `retry` keyword
|
|
611
|
+
if(previous_trace->rescued && strcmp(trace->method_name, previous_trace->method_name) == 0 && strcmp(trace->method_owner.name, previous_trace->method_owner.name) == 0) {
|
|
612
|
+
//fprintf(stderr, "[%ld] detected retry of method %s\n", fiber_id, trace->method_name);
|
|
613
|
+
free_trace(trace);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Qfalse == 0x0
|
|
620
|
+
if(rb_funcall(original_defined_class, public_method_defined, 2, method_id, Qfalse)) {
|
|
621
|
+
trace->method_visibility = PUBLIC;
|
|
622
|
+
} else
|
|
623
|
+
if(rb_funcall(original_defined_class, private_method_defined, 2, method_id, Qfalse)) {
|
|
624
|
+
trace->method_visibility = PRIVATE;
|
|
625
|
+
} else {
|
|
626
|
+
trace->method_visibility = PROTECTED;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
assign_parameters(trace, trace_arg);
|
|
630
|
+
|
|
631
|
+
if(stack_pair == call_stacks.end()) {
|
|
632
|
+
// TODO-Racer: Might want to increase initial capacity
|
|
633
|
+
auto stack = std::vector<ReturnTrace*>();
|
|
634
|
+
stack.push_back(trace);
|
|
635
|
+
// fprintf(stderr, "[%ld] inserting method %s#%s\n", fiber_id, trace->method_owner_name, trace->method_name);
|
|
636
|
+
call_stacks.insert({ fiber_id, stack });
|
|
637
|
+
} else {
|
|
638
|
+
// fprintf(stderr, "[%ld] pushing method %s#%s\n", fiber_id, trace->method_owner_name, trace->method_name);
|
|
639
|
+
auto& stack = (*stack_pair).second;
|
|
640
|
+
stack.push_back(trace);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
static void
|
|
645
|
+
process_return_event(rb_trace_arg_t* trace_arg) {
|
|
646
|
+
long fiber_id = rb_fiber_current();
|
|
647
|
+
|
|
648
|
+
auto stack_pair = call_stacks.find(fiber_id);
|
|
649
|
+
if(stack_pair == call_stacks.end()) {
|
|
650
|
+
// This might happen if another thread started calling before our TracePoint was enabled
|
|
651
|
+
// or if Racer.start was called in a method that returns before Racer.stop was called
|
|
652
|
+
debug_warn("[%ld] Unexpected: No callstack for return of %s", fiber_id, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
auto& stack = (*stack_pair).second;
|
|
657
|
+
|
|
658
|
+
if(stack.empty()) {
|
|
659
|
+
// This might happen if the method call that returns now activated our tracepoint
|
|
660
|
+
debug_warn("[%ld] Unexpected: Call stack empty for method %s\n", fiber_id, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
auto* trace = stack.back();
|
|
665
|
+
if(!trace) {
|
|
666
|
+
rb_warn("[%ld] Unexpected: Trace is null for method: %s", fiber_id, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if(!trace->method_name) {
|
|
671
|
+
rb_warn("Trying to return from method %s while a block is still on the stack", rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if(strcmp(trace->method_name, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg)))) != 0) {
|
|
675
|
+
// This could happen if we return from a function for which we do not have a call recorded
|
|
676
|
+
// def foo -> not recorded
|
|
677
|
+
// Racer.start
|
|
678
|
+
// end -> recorded but no call stack entry
|
|
679
|
+
rb_warn("[%ld] Return: Method mismatch, expected %s, got %s", fiber_id, trace->method_name, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
stack.pop_back();
|
|
684
|
+
//fprintf(stderr, "[%ld] popped method %s#%s\n", fiber_id, trace->method_owner_name, trace->method_name);
|
|
685
|
+
|
|
686
|
+
auto return_value = rb_tracearg_return_value(trace_arg);
|
|
687
|
+
trace->return_type = value_to_constant_instance(return_value);
|
|
688
|
+
|
|
689
|
+
if(trace->block_param.has_value()) {
|
|
690
|
+
auto block_param = trace->block_param.value();
|
|
691
|
+
VALUE tracepoint_id = block_param.tracepoint_id;
|
|
692
|
+
if(tracepoint_id) {
|
|
693
|
+
rb_tracepoint_disable(tracepoint_id);
|
|
694
|
+
block_param.tracepoint_id = 0;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
for(auto constant : constant_updates) {
|
|
699
|
+
trace->constant_updates.push_back(constant_map.at(constant));
|
|
700
|
+
}
|
|
701
|
+
constant_updates.clear();
|
|
702
|
+
|
|
703
|
+
tiny_queue_message_t *message = (tiny_queue_message_t *)malloc(sizeof(tiny_queue_message_t));
|
|
704
|
+
message->queue_state = 1;
|
|
705
|
+
message->data = trace;
|
|
706
|
+
tiny_queue_push(tiny_queue, message);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
static void
|
|
710
|
+
process_rescued_event(rb_trace_arg_t* trace_arg) {
|
|
711
|
+
long fiber_id = rb_fiber_current();
|
|
712
|
+
|
|
713
|
+
auto stack_pair = call_stacks.find(fiber_id);
|
|
714
|
+
if(stack_pair != call_stacks.end()) {
|
|
715
|
+
auto& stack = (*stack_pair).second;
|
|
716
|
+
|
|
717
|
+
if(stack.empty()) {
|
|
718
|
+
// This might happen if the method call that returns now activated our tracepoint
|
|
719
|
+
rb_warn("[%ld] Unexpected: Call stack empty for method %s\n", fiber_id, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
auto trace = stack.back();
|
|
724
|
+
|
|
725
|
+
if(!trace) {
|
|
726
|
+
rb_warn("[%ld] Unexpected: Trace is null for method: %s", fiber_id, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg))));
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if(!trace->method_name || strcmp(trace->method_name, rb_id2name(SYM2ID(rb_tracearg_method_id(trace_arg)))) != 0) {
|
|
731
|
+
// If these do not match the rescue happens inside a block and not inside the method. The method is not being retried in this case so we ignore this event.
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// auto exception = rb_inspect(rb_tracearg_raised_exception(trace_arg));
|
|
736
|
+
//fprintf(stderr, "[%ld] setting rescued to true for method %s#%s, exception: %s\n", fiber_id, trace->method_name, trace->method_owner_name, StringValueCStr(exception));
|
|
737
|
+
trace->rescued = true;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
static void
|
|
742
|
+
process_block_call_event(rb_trace_arg_t* trace_arg, ReturnTrace* last_trace) {
|
|
743
|
+
if(!last_trace->block_param.has_value()) {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
auto& block_param = *(last_trace->block_param);
|
|
748
|
+
/*
|
|
749
|
+
We cannot correctly trace multiple block calls.
|
|
750
|
+
|
|
751
|
+
Why? Because ruby applies tracepoints recursively to all blocks defined inside our block. If those "inner"
|
|
752
|
+
blocks are being called outside of the block, we cannot detect if the block is the block we actually want to trace
|
|
753
|
+
or if it was a block defined inside the block we want to trace.
|
|
754
|
+
|
|
755
|
+
Example:
|
|
756
|
+
|
|
757
|
+
$foo = nil
|
|
758
|
+
def bar(&block)
|
|
759
|
+
$foo = block
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def foo(&block)
|
|
763
|
+
block.call
|
|
764
|
+
$foo.call <--- we cannot differentiate this block call from calls to the block variable
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
$tp_call.enable(target: $block)
|
|
768
|
+
block = ->(*args) {
|
|
769
|
+
bar do
|
|
770
|
+
puts "hi"
|
|
771
|
+
end
|
|
772
|
+
}
|
|
773
|
+
p foo(&block)
|
|
774
|
+
|
|
775
|
+
The only thing we can say for sure is that the first call to the block will be the one we actually want to trace.
|
|
776
|
+
For now we keep the possibility to maybe have multiple block traces. Maybe in the future we find a possibility to
|
|
777
|
+
trace multiple calls for the same block.
|
|
778
|
+
*/
|
|
779
|
+
if(block_param.block_traces.size() > 0) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
ReturnTrace *trace = nullptr;
|
|
784
|
+
if(block_param.current_block_call_stack.empty()) {
|
|
785
|
+
trace = new ReturnTrace;
|
|
786
|
+
|
|
787
|
+
// Note: we do not need to track rescued state for blocks as the :b_call event is only
|
|
788
|
+
// executed once even if the block is being retried
|
|
789
|
+
trace->rescued = false;
|
|
790
|
+
|
|
791
|
+
assign_parameters(trace, trace_arg);
|
|
792
|
+
|
|
793
|
+
auto self = rb_tracearg_self(trace_arg);
|
|
794
|
+
trace->block_self_type = value_to_constant_instance(self);
|
|
795
|
+
}
|
|
796
|
+
block_param.current_block_call_stack.push_back(trace);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
static void
|
|
800
|
+
process_block_return_event(rb_trace_arg_t* trace_arg, ReturnTrace* last_trace) {
|
|
801
|
+
if(!last_trace->block_param.has_value()) {
|
|
802
|
+
rb_warn("Block return for method without block param (%s)", last_trace->method_name);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
auto &block_param = last_trace->block_param.value();
|
|
807
|
+
// See comment in process_block_call_event for more info on why we need to to restrict ourselves to one block trace
|
|
808
|
+
if(block_param.block_traces.size() > 0) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (block_param.current_block_call_stack.empty()) {
|
|
813
|
+
rb_warn("Current block call stack for method %s is empty.", last_trace->method_name);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
auto last_block_call = block_param.current_block_call_stack.back();
|
|
817
|
+
block_param.current_block_call_stack.pop_back();
|
|
818
|
+
|
|
819
|
+
if(!last_block_call) {
|
|
820
|
+
/*
|
|
821
|
+
* This is a inner block call (so a block call inside a block of our method call)
|
|
822
|
+
* For example
|
|
823
|
+
* foo do <- actual block call, ReturnTrace on stack
|
|
824
|
+
* ->().call <- inner block call, added nullptr to stack
|
|
825
|
+
* end
|
|
826
|
+
*/
|
|
827
|
+
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
auto return_value = rb_tracearg_return_value(trace_arg);
|
|
832
|
+
last_block_call->return_type = value_to_constant_instance(return_value);
|
|
833
|
+
block_param.block_traces.push_back(last_block_call);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
static void
|
|
837
|
+
process_tracepoint(VALUE trace_point, void *data)
|
|
838
|
+
{
|
|
839
|
+
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(trace_point);
|
|
840
|
+
|
|
841
|
+
if(!should_process_event(trace_arg)) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
switch(rb_tracearg_event_flag(trace_arg)) {
|
|
846
|
+
case RUBY_EVENT_CALL:
|
|
847
|
+
process_call_event(trace_arg);
|
|
848
|
+
break;
|
|
849
|
+
case RUBY_EVENT_RETURN:
|
|
850
|
+
process_return_event(trace_arg);
|
|
851
|
+
break;
|
|
852
|
+
case RUBY_EVENT_RESCUE:
|
|
853
|
+
process_rescued_event(trace_arg);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
static VALUE start(VALUE self, VALUE arg_pathMatcher, VALUE arg_maxGenericDepth)
|
|
859
|
+
{
|
|
860
|
+
if(!RB_NIL_P(arg_pathMatcher)) {
|
|
861
|
+
Check_Type(arg_pathMatcher, T_REGEXP);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if(rb_equal(pathMatcher, arg_pathMatcher) == Qfalse) {
|
|
865
|
+
matched_paths.clear();
|
|
866
|
+
}
|
|
867
|
+
pathMatcher = arg_pathMatcher;
|
|
868
|
+
|
|
869
|
+
Check_Type(arg_maxGenericDepth, T_FIXNUM);
|
|
870
|
+
maxGenericDepth = rb_fix2long(arg_maxGenericDepth);
|
|
871
|
+
|
|
872
|
+
if(!RB_NIL_P(tpCall)) {
|
|
873
|
+
rb_tracepoint_enable(tpCall);
|
|
874
|
+
return Qnil;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
socketFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
878
|
+
if (socketFd < 0) {
|
|
879
|
+
// handle error
|
|
880
|
+
perror("socket");
|
|
881
|
+
return Qnil;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
struct sockaddr_un sockaddr;
|
|
885
|
+
sockaddr.sun_family = AF_UNIX;
|
|
886
|
+
auto path = "/tmp/racer.sock";
|
|
887
|
+
strcpy(sockaddr.sun_path, path);
|
|
888
|
+
|
|
889
|
+
if (connect(socketFd, (struct sockaddr*) &sockaddr, sizeof(sockaddr)) < 0) {
|
|
890
|
+
perror("connect");
|
|
891
|
+
// This ensures that we can still flush, even if the socket connection did not work out.
|
|
892
|
+
// We may want to add more sophisticated error handling.
|
|
893
|
+
socketFd = 0;
|
|
894
|
+
return Qnil;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
tiny_queue = tiny_queue_create();
|
|
898
|
+
auto *worker_data = (struct WorkerData*) malloc(sizeof(struct WorkerData));
|
|
899
|
+
worker_data->queue = tiny_queue;
|
|
900
|
+
worker_data->socket_fd = socketFd;
|
|
901
|
+
pthread_create(&pthread, nullptr, init_worker, worker_data);
|
|
902
|
+
pthread_setname_np(pthread, "mworker");
|
|
903
|
+
|
|
904
|
+
tpCall = rb_tracepoint_new(Qnil, RUBY_EVENT_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_RESCUE, process_tracepoint, nullptr);
|
|
905
|
+
rb_tracepoint_enable(tpCall);
|
|
906
|
+
|
|
907
|
+
return Qnil;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
static VALUE stop(VALUE self)
|
|
911
|
+
{
|
|
912
|
+
if(RB_TEST(tpCall)) {
|
|
913
|
+
rb_tracepoint_disable(tpCall);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
for(auto &stack_pair : call_stacks) {
|
|
917
|
+
auto &stack = stack_pair.second;
|
|
918
|
+
while(!stack.empty()) {
|
|
919
|
+
auto* trace = stack.back();
|
|
920
|
+
stack.pop_back();
|
|
921
|
+
if(!trace) continue;
|
|
922
|
+
|
|
923
|
+
if(trace->block_param.has_value()) {
|
|
924
|
+
auto block_param = trace->block_param.value();
|
|
925
|
+
if(block_param.tracepoint_id != 0) {
|
|
926
|
+
rb_tracepoint_disable(block_param.tracepoint_id);
|
|
927
|
+
block_param.tracepoint_id = 0;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
free_trace(trace);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
call_stacks.clear();
|
|
935
|
+
|
|
936
|
+
return Qnil;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
static void flush_end(VALUE arg)
|
|
940
|
+
{
|
|
941
|
+
if (socketFd <= 0)
|
|
942
|
+
return;
|
|
943
|
+
|
|
944
|
+
stop(arg);
|
|
945
|
+
tpCall = Qnil;
|
|
946
|
+
|
|
947
|
+
struct tiny_queue_message_t *message = (struct tiny_queue_message_t *)malloc(sizeof(struct tiny_queue_message_t));
|
|
948
|
+
message->queue_state = 0;
|
|
949
|
+
tiny_queue_push(tiny_queue, message);
|
|
950
|
+
|
|
951
|
+
pthread_join(pthread, nullptr);
|
|
952
|
+
tiny_queue_destroy(tiny_queue);
|
|
953
|
+
|
|
954
|
+
const char* buffer = "stop";
|
|
955
|
+
if(send(socketFd, buffer, strlen(buffer) + 1, 0) < 0) {
|
|
956
|
+
perror("socket send flush");
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if(shutdown(socketFd, SHUT_WR) < 0) {
|
|
960
|
+
perror("socket shutdown");
|
|
961
|
+
}
|
|
962
|
+
close(socketFd);
|
|
963
|
+
socketFd = -1;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
static VALUE flush(VALUE self)
|
|
967
|
+
{
|
|
968
|
+
flush_end(self);
|
|
969
|
+
return Qnil;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
extern "C" void
|
|
973
|
+
Init_racer(void)
|
|
974
|
+
{
|
|
975
|
+
Racer = rb_define_module("Racer");
|
|
976
|
+
|
|
977
|
+
reqParam = rb_intern("req");
|
|
978
|
+
optParam = rb_intern("opt");
|
|
979
|
+
restParam = rb_intern("rest");
|
|
980
|
+
keyreqParam = rb_intern("keyreq");
|
|
981
|
+
keyParam = rb_intern("key");
|
|
982
|
+
keyrestParam = rb_intern("keyrest");
|
|
983
|
+
nokeyParam = rb_intern("nokey");
|
|
984
|
+
blockParam = rb_intern("block");
|
|
985
|
+
anonRest = rb_intern("*");
|
|
986
|
+
anonKeyrest = rb_intern("**");
|
|
987
|
+
anonBlock = rb_intern("&");
|
|
988
|
+
method_defined = rb_intern("method_defined?");
|
|
989
|
+
public_method_defined = rb_intern("public_method_defined?");
|
|
990
|
+
private_method_defined = rb_intern("private_method_defined?");
|
|
991
|
+
callerLocationsId = rb_intern("caller_locations");
|
|
992
|
+
pathId = rb_intern("path");
|
|
993
|
+
rb_global_variable(&tpCall);
|
|
994
|
+
rb_global_variable(&pathMatcher);
|
|
995
|
+
|
|
996
|
+
rb_define_singleton_method(Racer, "__c_start", start, 2);
|
|
997
|
+
rb_define_singleton_method(Racer, "stop", stop, 0);
|
|
998
|
+
rb_define_singleton_method(Racer, "flush", flush, 0);
|
|
999
|
+
}
|