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.
@@ -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
+ }