arg_scanner 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d7016d5a2dddb4f8fd0be218fa11d5e1b65addeb
4
- data.tar.gz: c7b233ada70e04c77b73f4b179cda89815f6f16b
2
+ SHA256:
3
+ metadata.gz: 630a4491a80316eccf60cecf3306b8a9efe9344d8604e552a39a8a2fd1616790
4
+ data.tar.gz: 4365cc9d2dfe5a347423dd73c810d60957fec46bd092acda0e3cad08d358a1eb
5
5
  SHA512:
6
- metadata.gz: c049b2d855fc27e37ff6feb1edad6713644d3955ce6780dc2b052ace6eea1c9afe2b010441106d437a568a52acb7ead358d0565648c85bb5def13ce67e82bf6a
7
- data.tar.gz: bd3b07970807bff87c370be1fbc14acd9042a2e42090769056e6f7782ef0c02dc36f13375f5284350534bbfa6234f61871bb12643c4a9c9e71c57e3c0772de56
6
+ metadata.gz: 9cbfb9b78a501993d0fc8f42777cda5976e7c4d9dcfe79242991324371ed173baba0fb6f8f1f7238697996ffa27ddc0f623c83e5566bb56c5e439aef3eab69a5
7
+ data.tar.gz: c3cc36ef3faa5c9c8e09bcc91b00ca1743d6a9ab2d3201f8bcbeb8350f7ffe5076e65f5024150ba1e5805eb3e57ba9b3efe16cbf0a898633706d72f56411cd13
data/README.md CHANGED
@@ -9,8 +9,13 @@ deliver the following information:
9
9
  This information can be used then to calculate and use type contracts
10
10
  for the analysed methods.
11
11
 
12
- ## Installation
12
+ ## Requirements
13
+
14
+ ##### libglib2.0-dev
13
15
 
16
+ On Ubuntu run: ` $ sudo apt install libglib2.0-dev`
17
+
18
+ ## Installation
14
19
 
15
20
  `arg_scanner` is meant to be used as a binary to run any other ruby executable
16
21
  manually so including it in the `Gemfile` is not necessary.
@@ -14,25 +14,12 @@ option_parser = OptionParser.new do |opts|
14
14
  EOB
15
15
 
16
16
  opts.separator "Options:"
17
- opts.on("-r", "--root=[ROOT]", String, "local project root(s) to distinguish from library sources (path1[:pathn]*)") do |paths|
18
- options.project_roots = paths.split ':' if paths
19
- end
20
- opts.on("--local=[VERSION]", Integer,
21
- "local source treatment: mark as fake gem with given VERSION, default: 0") do |local|
22
- options.local_version = local.to_s
23
- end
24
- opts.on("--no-local", "local source treatment: ignore, do not send data from local sources") do
25
- options.no_local = true
26
- end
27
17
  opts.on("--type-tracker", "enable type tracker") do
28
18
  options.enable_type_tracker = true
29
19
  end
30
20
  opts.on("--state-tracker", "enable state tracker") do
31
21
  options.enable_state_tracker = true
32
22
  end
33
- opts.on("--return-type-tracker", "enable return type tracker") do
34
- options.enable_return_type_tracker = true
35
- end
36
23
 
37
24
  opts.on("--no-type-tracker", "disable type tracker") do
38
25
  options.enable_type_tracker = false
@@ -40,14 +27,27 @@ option_parser = OptionParser.new do |opts|
40
27
  opts.on("--no-state-tracker", "disable state tracker") do
41
28
  options.enable_state_tracker = false
42
29
  end
43
- opts.on("--no-return-type-tracker", "disable return type tracker") do
44
- options.enable_return_type_tracker = false
45
- end
46
30
 
47
31
  opts.on("--output-dir=[Dir]", String, "specify output directory (ignored by type tracker)") do |dir|
48
32
  options.output_dir = dir
49
33
  end
50
34
 
35
+ opts.on("--catch-only-every-N-call=[N]", Integer, "randomly catches only 1/N of all calls to speed up performance (by default N = 1)") do |n|
36
+ options.catch_only_every_n_call = n
37
+ end
38
+ opts.on("--project-root=[PATH]", String, "Specify project's root directory to catch every call from this directory. "\
39
+ "Calls from other directories aren't guaranteed to be caught") do |path|
40
+ options.project_root = path
41
+ end
42
+
43
+ opts.on("--pipe-file-path=[PATH]", String, "Specify pipe file path to connect to server") do |path|
44
+ options.pipe_file_path = path
45
+ end
46
+
47
+ opts.on("--buffering", "enable buffering between arg-scanner and server. It speeds up arg-scanner but doesn't allow "\
48
+ "to use arg-scanner \"interactively\". Disabled by default") do |buffering|
49
+ options.buffering = buffering
50
+ end
51
51
  end
52
52
 
53
53
  begin
@@ -69,7 +69,7 @@ end
69
69
  options.set_env
70
70
 
71
71
  old_opts = ENV['RUBYOPT'] || ''
72
- starter = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/arg_scanner/starter"
72
+ starter = "-r #{File.expand_path(File.dirname(__FILE__))}/../lib/arg_scanner/starter"
73
73
  unless old_opts.include? starter
74
74
  ENV['RUBYOPT'] = starter
75
75
  ENV['RUBYOPT'] += " #{old_opts}" if old_opts != ''
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # This is small script for launching type tracker under RubyMine's provided server. Acts like arg-scanner wrapper
3
+ require 'optparse'
4
+ require 'arg_scanner/version'
5
+ require 'tmpdir'
6
+ require 'json'
7
+
8
+ option_parser = OptionParser.new do |opts|
9
+ opts.banner = <<~EOB
10
+ rubymine-type-tracker #{ArgScanner::VERSION}
11
+
12
+ Usage: rubymine-type-tracker <ruby script to execute>
13
+ rubymine-type-tracker is a ruby script for easy launching some command under
14
+ RubyMine's type tracker. The data will be sent to a server run by RubyMine.
15
+ So before launching this script be sure project is opened in RubyMine with
16
+ "Ruby Dynamic Code Insight" plugin installed.
17
+ EOB
18
+ end
19
+
20
+ begin
21
+ option_parser.parse! ARGV
22
+ if ARGV.size == 0
23
+ raise StandardError.new("")
24
+ end
25
+ rescue StandardError => e
26
+ puts option_parser
27
+ exit 1
28
+ end
29
+
30
+ dot_ruby_type_inference_dir = File.join(Dir.tmpdir, ".ruby-type-inference")
31
+ if File.directory?(dot_ruby_type_inference_dir)
32
+ match_jsons = Dir.foreach(dot_ruby_type_inference_dir).map do |file_name|
33
+ if file_name == '.' || file_name == '..'
34
+ next nil
35
+ end
36
+ json = JSON.parse(IO.read(File.join(dot_ruby_type_inference_dir, file_name)))
37
+ if json["projectPath"] != Dir.pwd
38
+ next nil
39
+ end
40
+ next json
41
+ end.select { |x| x != nil }
42
+ else
43
+ match_jsons = []
44
+ end
45
+
46
+ if match_jsons.count == 1
47
+ json = match_jsons[0]
48
+ elsif match_jsons.count > 1
49
+ STDERR.puts <<~EOB
50
+ Critical error! You may try to:\n
51
+ 1. Close RubyMine
52
+ 2. Clean #{dot_ruby_type_inference_dir}
53
+ 3. Open RubyMine
54
+ EOB
55
+ exit 1
56
+ elsif match_jsons.count == 0
57
+ STDERR.puts <<~EOB
58
+ Error! You are possibly...
59
+ * launching this script under directory different from project
60
+ opened in RubyMine (please `cd` to dir firstly)
61
+ * haven't opened project in RubyMine
62
+ * haven't installed "Ruby Dynamic Code Insight" plugin in RubyMine
63
+ EOB
64
+ exit 1
65
+ end
66
+
67
+ to_exec = ["arg-scanner",
68
+ "--type-tracker",
69
+ "--project-root=#{json["projectPath"]}",
70
+ "--pipe-file-path=#{json["pipeFilePath"]}",
71
+ *ARGV]
72
+
73
+ Kernel.exec(*to_exec)
@@ -5,13 +5,19 @@
5
5
  #include <string.h>
6
6
  #include <assert.h>
7
7
  #include <stdarg.h>
8
+ #include <netinet/in.h>
9
+ #include <glib.h>
8
10
 
9
11
  //#define DEBUG_ARG_SCANNER 1
10
12
 
11
13
  #if RUBY_API_VERSION_CODE >= 20500
14
+ #if (RUBY_RELEASE_YEAR == 2017 && RUBY_RELEASE_MONTH == 10 && RUBY_RELEASE_DAY == 10) //workaround for 2.5.0-preview1
12
15
  #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec.cfp)
16
+ #else
17
+ #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec->cfp)
18
+ #endif
13
19
  #else
14
- #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->cfp)
20
+ #define TH_CFP(thread) ((rb_control_frame_t *)(thread)->cfp)
15
21
  #endif
16
22
 
17
23
  #ifdef DEBUG_ARG_SCANNER
@@ -28,64 +34,241 @@ int types_ids[20];
28
34
 
29
35
  static VALUE c_signature;
30
36
 
37
+ /**
38
+ * Contains info related to explicitly passed args
39
+ * For example:
40
+ * def foo(a, b = 1); end
41
+ *
42
+ * `b` passed here implicitly:
43
+ * foo(1)
44
+ *
45
+ * But here explicitly:
46
+ * foo(1, 10)
47
+ */
31
48
  typedef struct
32
49
  {
33
- ssize_t call_info_argc;
34
- char* call_info_kw_args;
50
+ ssize_t call_info_explicit_argc; // Number of arguments that was explicitly passed by user
51
+ char **call_info_kw_explicit_args; // kw arguments names that was explicitly passed by user (null terminating array)
35
52
  } call_info_t;
36
53
 
37
54
  typedef struct
38
55
  {
39
- VALUE method_name;
40
- char* args_info;
41
- VALUE path;
42
- char* call_info_kw_args;
43
- ssize_t call_info_argc;
56
+ char *receiver_name;
57
+ char *method_name;
58
+ char *args_info;
59
+ char *path;
60
+ char *return_type_name;
61
+ ssize_t explicit_argc; // Number of arguments that was explicitly passed by user
44
62
  int lineno;
63
+ int is_in_project_root; // Can be 0, 1 or -1 when project_root is not specified
45
64
  } signature_t;
46
65
 
47
66
  void Init_arg_scanner();
48
67
 
49
- static char* get_args_info();
50
- static VALUE handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path);
51
- static VALUE handle_return(VALUE self, VALUE signature, VALUE return_type_name);
68
+ static const char *ARG_SCANNER_EXIT_COMMAND = "EXIT";
69
+ static const char *EMPTY_VALUE = "";
70
+ static const int MAX_NUMBER_OF_MISSED_CALLS = 10;
71
+ /**
72
+ * There we keep information about signatures that have already been sent to server in order to not sent them again
73
+ */
74
+ static GTree *sent_to_server_tree;
75
+ /**
76
+ * Here we store map with key: signature_t and value: int number (how many times method was called with the same args)
77
+ * If we got that any method is called with the same args more than MAX_NUMBER_OF_MISSED_CALLS times in a row then
78
+ * we will ignore it.
79
+ */
80
+ static GTree *number_missed_calls_tree;
81
+ static GSList *call_stack = NULL;
82
+ static char *get_args_info(const char *const *explicit_kw_args);
83
+ static VALUE handle_call(VALUE self, VALUE tp);
84
+ static VALUE handle_return(VALUE self, VALUE tp);
85
+ static VALUE destructor(VALUE self);
86
+ static const char *calc_sane_class_name(VALUE ptr);
87
+
88
+ // returns Qnil if ready; or string containing error message otherwise
89
+ static VALUE check_if_arg_scanner_ready(VALUE self);
52
90
 
53
91
  // For testing
54
92
  static VALUE get_args_info_rb(VALUE self);
55
93
  static VALUE get_call_info_rb(VALUE self);
56
94
 
57
- static call_info_t* get_call_info();
95
+ static call_info_t get_call_info();
58
96
  static bool is_call_info_needed();
59
97
 
60
- static void call_info_t_free(void *s)
98
+ static void call_info_t_free(call_info_t s)
61
99
  {
62
- if (((call_info_t *)s)->call_info_kw_args != 0)
63
- free(((call_info_t *)s)->call_info_kw_args);
64
- free(s);
100
+ free(s.call_info_kw_explicit_args);
65
101
  }
66
102
 
67
- static void signature_t_free(void *s)
103
+ static void signature_t_free(signature_t *s)
68
104
  {
69
- if (((signature_t *)s)->args_info != 0)
70
- free(((signature_t *)s)->args_info);
71
- if (((signature_t *)s)->call_info_kw_args != 0)
72
- free(((signature_t *)s)->call_info_kw_args);
105
+ free(s->receiver_name);
106
+ free(s->method_name);
107
+ free(s->args_info);
108
+ free(s->path);
109
+ free(s->return_type_name);
73
110
  free(s);
74
111
  }
75
112
 
76
- static void
77
- signature_t_mark(signature_t *sig)
113
+ // Free signature_t partially leaving parts that are used in sent_to_server_tree_comparator
114
+ // @see_also sent_to_server_tree_comparator
115
+ static void signature_t_free_partially(signature_t *s)
78
116
  {
79
- rb_gc_mark(sig->method_name);
80
- rb_gc_mark(sig->path);
117
+ free(s->receiver_name);
118
+ s->receiver_name = NULL;
119
+
120
+ free(s->method_name);
121
+ s->method_name = NULL;
122
+ }
123
+
124
+ // Comparator for number_missed_calls_tree.
125
+ static gint
126
+ number_missed_calls_tree_comparator(gconstpointer x, gconstpointer y, gpointer user_data_ignored) {
127
+ const signature_t *a = x;
128
+ const signature_t *b = y;
129
+ int ret;
130
+
131
+ // Comparison using lineno and path theoretically should guarantees us unique.
132
+ // And compare lineno firstly because it's faster O(1) than comparing path which is O(path_len)
133
+ ret = a->lineno - b->lineno;
134
+ if (ret != 0) return ret;
135
+
136
+ ret = strcmp(a->path, b->path);
137
+ if (ret != 0) return ret;
138
+
139
+ return 0;
140
+ }
141
+
142
+ // Comparator for sent_to_server_tree.
143
+ // If you want to change the way it compare then don't forget to
144
+ // change signature_t_free_partially accordingly
145
+ // @see_also signature_t_free_partially
146
+ static gint
147
+ sent_to_server_tree_comparator(gconstpointer x, gconstpointer y, gpointer user_data_ignored) {
148
+ const signature_t *a = x;
149
+ const signature_t *b = y;
150
+ int ret;
151
+
152
+ ret = number_missed_calls_tree_comparator(x, y, user_data_ignored);
153
+ if (ret != 0) return ret;
154
+
155
+ if (a->args_info != NULL && b->args_info != NULL) {
156
+ ret = strcmp(a->args_info, b->args_info);
157
+ if (ret != 0) return ret;
158
+ }
159
+
160
+ ret = strcmp(a->return_type_name, b->return_type_name);
161
+ if (ret != 0) return ret;
162
+
163
+ return 0;
164
+ }
165
+
166
+ inline int start_with(const char *str, const char *prefix) {
167
+ if (str == NULL || prefix == NULL) {
168
+ return -1;
169
+ }
170
+ while (*str != '\0' && *prefix != '\0') {
171
+ if (*str != *prefix) {
172
+ return 0;
173
+ }
174
+ str++;
175
+ prefix++;
176
+ }
177
+ return 1;
178
+ }
179
+
180
+ FILE *pipe_file = NULL;
181
+ static char *project_root = NULL;
182
+ static int catch_only_every_n_call = 1;
183
+
184
+ static int file_exists(const char *file_path) {
185
+ return access(file_path, F_OK) != -1;
186
+ }
187
+
188
+ static VALUE init(VALUE self, VALUE pipe_file_path, VALUE buffering,
189
+ VALUE project_root_local, VALUE catch_only_every_n_call_local) {
190
+ if (pipe_file_path != Qnil) {
191
+ pipe_file_path = rb_file_s_expand_path(1, &pipe_file_path); // https://ruby-doc.org/core-2.2.0/File.html#method-c-expand_path
192
+ const char *pipe_file_path_c = StringValueCStr(pipe_file_path);
193
+ if (!file_exists(pipe_file_path_c)) {
194
+ fprintf(stderr, "Specified pipe file: %s doesn't exists\n", pipe_file_path_c);
195
+ exit(1);
196
+ }
197
+ pipe_file = fopen(pipe_file_path_c, "w");
198
+ if (pipe_file == NULL) {
199
+ fprintf(stderr, "Cannot open pipe file \"%s\" with write access\n", pipe_file_path_c);
200
+ exit(1);
201
+ }
202
+
203
+ int buffering_disabled = buffering == Qnil;
204
+ if (buffering_disabled) {
205
+ setbuf(pipe_file, NULL);
206
+ }
207
+ }
208
+ if (project_root_local != Qnil) {
209
+ project_root = strdup(StringValueCStr(project_root_local));
210
+ }
211
+ if (catch_only_every_n_call_local != Qnil) {
212
+ if (sscanf(StringValueCStr(catch_only_every_n_call_local), "%d", &catch_only_every_n_call) != 1) {
213
+ fprintf(stderr, "Please specify number in --catch-only-every-N-call arg\n");
214
+ exit(1);
215
+ }
216
+ srand(time(0));
217
+ }
218
+ return Qnil;
81
219
  }
82
220
 
83
221
  void Init_arg_scanner() {
84
222
  mArgScanner = rb_define_module("ArgScanner");
85
- rb_define_module_function(mArgScanner, "handle_call", handle_call, 3);
86
- rb_define_module_function(mArgScanner, "handle_return", handle_return, 2);
223
+ rb_define_module_function(mArgScanner, "handle_call", handle_call, 1);
224
+ rb_define_module_function(mArgScanner, "handle_return", handle_return, 1);
87
225
  rb_define_module_function(mArgScanner, "get_args_info", get_args_info_rb, 0);
88
226
  rb_define_module_function(mArgScanner, "get_call_info", get_call_info_rb, 0);
227
+ rb_define_module_function(mArgScanner, "destructor", destructor, 0);
228
+ rb_define_module_function(mArgScanner, "check_if_arg_scanner_ready", check_if_arg_scanner_ready, 0);
229
+ rb_define_module_function(mArgScanner, "init", init, 4);
230
+
231
+ sent_to_server_tree = g_tree_new_full(/*key_compare_func =*/sent_to_server_tree_comparator,
232
+ /*key_compare_data =*/NULL,
233
+ /*key_destroy_func =*/(GDestroyNotify)signature_t_free,
234
+ /*value_destroy_func =*/NULL);
235
+
236
+ // key_destroy_func is NULL because we will use the same keys for number_missed_calls_tree
237
+ // and sent_to_server_tree. And all memory management is done by sent_to_server_tree
238
+ number_missed_calls_tree = g_tree_new_full(/*key_compare_func =*/number_missed_calls_tree_comparator,
239
+ /*key_compare_data =*/NULL,
240
+ /*key_destroy_func =*/NULL,
241
+ /*value_destroy_func =*/NULL);
242
+ }
243
+
244
+ inline void push_to_call_stack(signature_t *signature) {
245
+ call_stack = g_slist_prepend(call_stack, (gpointer) signature);
246
+ }
247
+
248
+ inline signature_t *pop_from_call_stack() {
249
+ if (call_stack == NULL) {
250
+ return NULL;
251
+ }
252
+ signature_t *ret = (signature_t *) call_stack->data;
253
+
254
+ GSList *old_head = call_stack;
255
+ call_stack = g_slist_remove_link(call_stack, old_head);
256
+ g_slist_free_1(old_head);
257
+ return ret;
258
+ }
259
+
260
+ inline int is_call_stack_empty() {
261
+ return call_stack == NULL;
262
+ }
263
+
264
+ /**
265
+ * Looks at the object at the top of this stack without removing it from the stack.
266
+ */
267
+ inline signature_t *top_of_call_stack() {
268
+ if (call_stack == NULL) {
269
+ return NULL;
270
+ }
271
+ return (signature_t *) call_stack[0].data;
89
272
  }
90
273
 
91
274
  rb_control_frame_t *
@@ -100,186 +283,188 @@ my_rb_vm_get_binding_creatable_next_cfp(const rb_thread_t *th, const rb_control_
100
283
  return 0;
101
284
  }
102
285
 
286
+ static VALUE exit_from_handle_call_skipping_call() {
287
+ push_to_call_stack(NULL);
288
+ return Qnil;
289
+ }
290
+
103
291
  static VALUE
104
- handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path)
292
+ handle_call(VALUE self, VALUE tp)
105
293
  {
106
- //VALUE method_sym = rb_tracearg_method_id(trace_arg);
107
- //VALUE path = trace_arg->path;
108
- VALUE c_method_name = method_name;
109
- VALUE c_path = path;
110
- int c_lineno = FIX2INT(lineno);//trace_arg->lineno;
111
-
112
- signature_t *sign;
113
- sign = ALLOC(signature_t);
114
-
115
- sign->lineno = c_lineno;
116
- sign->method_name = c_method_name;
117
- sign->path = c_path;
294
+ signature_t sign_temp;
295
+ memset(&sign_temp, 0, sizeof(sign_temp));
296
+ sign_temp.lineno = FIX2INT(rb_funcall(tp, rb_intern("lineno"), 0)); // Convert Ruby's Fixnum to C language int
297
+ VALUE path = rb_funcall(tp, rb_intern("path"), 0);
298
+ path = rb_file_s_expand_path(1, &path); // https://ruby-doc.org/core-2.2.0/File.html#method-c-expand_path
299
+ sign_temp.path = StringValueCStr(path);
118
300
 
119
- #ifdef DEBUG_ARG_SCANNER
120
- LOG("Getting args info for %s %s %d \n", rb_id2name(SYM2ID(sign->method_name)), StringValuePtr(sign->path), sign->lineno);
121
- #endif
122
- sign->args_info = get_args_info();
123
-
124
- if (is_call_info_needed())
125
- {
126
- call_info_t *info = get_call_info();
301
+ int is_in_project_root = start_with(sign_temp.path, project_root);
127
302
 
128
- sign->call_info_argc = info->call_info_argc;
129
- sign->call_info_kw_args = info->call_info_kw_args;
303
+ if (project_root != NULL && !is_in_project_root) {
304
+ signature_t *peek = top_of_call_stack();
130
305
 
131
- free(info);
132
- } else {
133
- sign->call_info_argc = -1;
134
- sign->call_info_kw_args = 0;
306
+ if (!is_call_stack_empty() && (peek == NULL || !(peek->is_in_project_root))) {
307
+ return exit_from_handle_call_skipping_call();
308
+ }
135
309
  }
136
310
 
137
- //return Data_Wrap_Struct(c_signature, signature_t_mark, signature_t_free, sign);
138
- return Data_Wrap_Struct(c_signature, signature_t_mark, xfree, sign);
139
- }
140
-
141
- static VALUE
142
- handle_return(VALUE self, VALUE signature, VALUE return_type_name)
143
- {
144
- signature_t *sign;
145
- const char *args_info;
146
- const char *call_info_kw_args;
147
- char json_mes[2000];
311
+ if (project_root == NULL || !is_in_project_root) {
312
+ int number_of_missed_calls = (int)g_tree_lookup(number_missed_calls_tree, &sign_temp);
313
+ if (number_of_missed_calls > MAX_NUMBER_OF_MISSED_CALLS) {
314
+ return exit_from_handle_call_skipping_call();
315
+ }
316
+ }
148
317
 
149
- Data_Get_Struct(signature, signature_t, sign);
318
+ if (catch_only_every_n_call != 1 && rand() % catch_only_every_n_call != 0) {
319
+ return exit_from_handle_call_skipping_call();
320
+ }
150
321
 
151
- args_info = sign->args_info;
152
- if (!args_info)
153
- args_info = "";
154
- call_info_kw_args = sign->call_info_kw_args;
155
- if (!call_info_kw_args)
156
- call_info_kw_args = "";
322
+ signature_t *sign = (signature_t *) calloc(1, sizeof(*sign));
157
323
 
324
+ sign->is_in_project_root = is_in_project_root;
325
+ sign->lineno = sign_temp.lineno;
326
+ sign->path = strdup(sign_temp.path);
327
+ sign->method_name = strdup(rb_id2name(SYM2ID(rb_funcall(tp, rb_intern("method_id"), 0))));
328
+ sign->explicit_argc = -1;
158
329
 
159
330
  #ifdef DEBUG_ARG_SCANNER
160
- LOG("%s \n", rb_id2name(SYM2ID(sign->method_name)));
161
- LOG("%d \n", sign->call_info_argc);
162
- LOG("%s \n", call_info_kw_args);
163
- LOG("%s \n", args_info);
164
- LOG("%s \n", StringValuePtr(sign->path));
165
- LOG("%d \n", sign->lineno);
331
+ LOG("Getting args info for %s %s %d \n", sign->method_name, sign->path, sign->lineno);
166
332
  #endif
333
+ call_info_t info;
334
+ info.call_info_kw_explicit_args = NULL;
335
+ if (is_call_info_needed()) {
336
+ info = get_call_info();
337
+ sign->explicit_argc = info.call_info_explicit_argc;
338
+ }
167
339
 
168
- assert(strlen(args_info) < 1000);
169
-
170
- snprintf(json_mes, 2000,
171
- "{\"method_name\":\"%s\",\"call_info_argc\":\"%d\",\"call_info_kw_args\":\"%s\",\"args_info\":\"%s\",\"visibility\":\"%s\",\"path\":\"%s\",\"lineno\":\"%d\",",
172
- rb_id2name(SYM2ID(sign->method_name)),
173
- sign->call_info_argc,
174
- call_info_kw_args,
175
- //StringValuePtr(receiver_name),
176
- args_info,
177
- //StringValuePtr(return_type_name),
178
- "PUBLIC",
179
- StringValuePtr(sign->path),
180
- sign->lineno);
340
+ sign->args_info = get_args_info(info.call_info_kw_explicit_args);
341
+ call_info_t_free(info);
181
342
 
182
- LOG("%s \n", json_mes);
343
+ if (sign->args_info != NULL && strlen(sign->args_info) >= 1000) {
344
+ signature_t_free(sign);
345
+ return exit_from_handle_call_skipping_call();
346
+ }
183
347
 
184
- return rb_str_new_cstr(json_mes);
348
+ push_to_call_stack(sign);
349
+ return Qnil;
185
350
  }
186
351
 
187
- static call_info_t*
188
- get_call_info()
352
+ static VALUE
353
+ handle_return(VALUE self, VALUE tp)
189
354
  {
190
- rb_thread_t *thread;
191
- rb_control_frame_t *cfp;
192
- call_info_t *info;
193
-
194
- thread = ruby_current_thread;
195
- cfp = TH_CFP(thread);
196
- info = malloc(sizeof(call_info_t));
197
- //info = ALLOC(call_info_t);
198
- //info = malloc;
355
+ signature_t *sign = pop_from_call_stack();
356
+ if (sign == NULL) {
357
+ return Qnil;
358
+ }
359
+ VALUE defined_class = rb_funcall(tp, rb_intern("defined_class"), 0);
199
360
 
200
- info->call_info_argc = -1;
201
- info->call_info_kw_args = 0;
361
+ VALUE receiver_name = rb_mod_name(defined_class);
202
362
 
203
- cfp += 4;
204
- cfp = my_rb_vm_get_binding_creatable_next_cfp(thread, cfp);
363
+ // if defined_class is nil then it means that method is invoked from anonymous module.
364
+ // Then trying to extract name of it's anonymous module. For more details see
365
+ // CallStatCompletionTest#testAnonymousModuleMethodCall
366
+ if (receiver_name == Qnil) {
367
+ VALUE this = rb_funcall(tp, rb_intern("self"), 0);
368
+ receiver_name = rb_funcall(this, rb_intern("to_s"), 0);
369
+ }
205
370
 
206
- if(cfp->iseq != NULL)
207
- {
208
- if(cfp->pc == NULL || cfp->iseq->body == NULL)
209
- {
210
- return info;
371
+ VALUE return_type_name = rb_funcall(tp, rb_intern("return_value"), 0);
372
+
373
+ sign->receiver_name = strdup(StringValueCStr(receiver_name));
374
+ sign->return_type_name = strdup(calc_sane_class_name(return_type_name));
375
+
376
+ signature_t *sign_in_sent_to_server_tree = g_tree_lookup(sent_to_server_tree, sign);
377
+ if (sign_in_sent_to_server_tree == NULL) {
378
+ // Resets number of missed calls to 0
379
+ g_tree_insert(number_missed_calls_tree, /*key = */sign, /*value = */0);
380
+
381
+ // GTree will free memory allocated by sign by itself
382
+ g_tree_insert(sent_to_server_tree, /*key = */sign, /*value = */sign);
383
+
384
+ if (pipe_file != NULL) {
385
+ fprintf(pipe_file,
386
+ "{\"method_name\":\"%s\",\"call_info_argc\":\"%d\",\"args_info\":\"%s\",\"visibility\":\"%s\","
387
+ "\"path\":\"%s\",\"lineno\":\"%d\",\"receiver_name\":\"%s\",\"return_type_name\":\"%s\"}\n",
388
+ sign->method_name,
389
+ sign->explicit_argc,
390
+ sign->args_info != NULL ? sign->args_info : "",
391
+ "PUBLIC",
392
+ sign->path,
393
+ sign->lineno,
394
+ sign->receiver_name,
395
+ sign->return_type_name);
211
396
  }
212
397
 
213
- const rb_iseq_t *iseq = cfp->iseq;
214
-
215
- ptrdiff_t pc = cfp->pc - cfp->iseq->body->iseq_encoded;
398
+ signature_t_free_partially(sign);
399
+ } else if (project_root == NULL || !sign->is_in_project_root) {
400
+ signature_t_free(sign);
216
401
 
217
- const VALUE *iseq_original = rb_iseq_original_iseq((rb_iseq_t *)iseq);
218
-
219
- int tmp = 0;
220
- int indent = 1;
221
-
222
- for(; indent < 6; indent++)
223
- {
224
- VALUE insn = iseq_original[pc - indent];
225
- tmp = (int)insn;
226
-
227
- if(0 < tmp && tmp < 256)
228
- {
229
- if(indent < 3)
230
- return info;
402
+ int found = (int) g_tree_lookup(number_missed_calls_tree, sign_in_sent_to_server_tree);
403
+ g_tree_insert(number_missed_calls_tree, /*key = */sign_in_sent_to_server_tree, /*value = */found + 1);
404
+ }
405
+ return Qnil;
406
+ }
231
407
 
232
- struct rb_call_info *ci = (struct rb_call_info *)iseq_original[pc - indent + 1];
408
+ static call_info_t
409
+ get_call_info() {
410
+ rb_thread_t *thread = ruby_current_thread;
411
+ rb_control_frame_t *cfp = TH_CFP(thread);
233
412
 
234
- info->call_info_argc = ci->orig_argc;
413
+ call_info_t empty;
414
+ empty.call_info_kw_explicit_args = NULL;
415
+ empty.call_info_explicit_argc = -1;
235
416
 
236
- if (ci->flag & VM_CALL_KWARG)
237
- {
238
- struct rb_call_info_kw_arg *kw_args = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
417
+ cfp += 3;
418
+ cfp = my_rb_vm_get_binding_creatable_next_cfp(thread, cfp);
239
419
 
240
- size_t kwArgSize = kw_args->keyword_len;
420
+ if(cfp->iseq == NULL || cfp->pc == NULL || cfp->iseq->body == NULL) {
421
+ return empty;
422
+ }
241
423
 
242
- VALUE kw_ary = rb_ary_new_from_values(kw_args->keyword_len, kw_args->keywords);
243
- const char *c_kw_ary[kwArgSize];
424
+ const rb_iseq_t *iseq = (const rb_iseq_t *) cfp->iseq;
244
425
 
245
- size_t ans_size = 0;
246
- int j;
426
+ ptrdiff_t pc = cfp->pc - cfp->iseq->body->iseq_encoded;
247
427
 
248
- for(j = kwArgSize - 1; j >= 0; j--)
249
- {
250
- VALUE kw = rb_ary_pop(kw_ary);
251
- const char* kw_name = rb_id2name(SYM2ID(kw));
428
+ const VALUE *iseq_original = rb_iseq_original_iseq(iseq);
252
429
 
253
- c_kw_ary[j] = kw_name;
254
- ans_size += strlen(kw_name);
430
+ int indent;
431
+ for (indent = 1; indent < 6; indent++) {
432
+ VALUE insn = iseq_original[pc - indent];
433
+ int tmp = (int)insn;
434
+ if(0 < tmp && tmp < 256) {
435
+ if(indent < 3) {
436
+ return empty;
437
+ }
438
+ call_info_t info;
439
+ struct rb_call_info *ci = (struct rb_call_info *)iseq_original[pc - indent + 1];
440
+ info.call_info_explicit_argc = ci->orig_argc;
441
+ info.call_info_kw_explicit_args = NULL;
255
442
 
256
- if((size_t)j + 1 < kwArgSize)
257
- ans_size++;
258
- }
443
+ if (ci->flag & VM_CALL_KWARG) {
444
+ struct rb_call_info_kw_arg *kw_args = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
259
445
 
260
- info->call_info_kw_args = (char*)malloc(ans_size + 1);
446
+ size_t kwArgSize = kw_args->keyword_len;
261
447
 
262
- if(kwArgSize > 0)
263
- {
264
- strcpy(info->call_info_kw_args, c_kw_ary[0]);
448
+ VALUE kw_ary = rb_ary_new_from_values(kw_args->keyword_len, kw_args->keywords);
265
449
 
266
- if(kwArgSize > 1)
267
- strcat(info->call_info_kw_args, ",");
268
- }
450
+ info.call_info_kw_explicit_args = (char **) malloc((kwArgSize + 1)*sizeof(*(info.call_info_kw_explicit_args)));
269
451
 
270
- for(j = 1; (size_t)j < kwArgSize; j++)
271
- {
272
- strcat(info->call_info_kw_args, c_kw_ary[j]);
452
+ int i;
453
+ for (i = kwArgSize -1 ; i >= 0; --i) {
454
+ VALUE kw = rb_ary_pop(kw_ary);
455
+ const char *kw_name = rb_id2name(SYM2ID(kw));
273
456
 
274
- if((size_t)j + 1 < kwArgSize)
275
- strcat(info->call_info_kw_args, ",");
276
- }
457
+ info.call_info_kw_explicit_args[i] = kw_name;
277
458
  }
278
- return info;
459
+ info.call_info_kw_explicit_args[kwArgSize] = NULL;
460
+ } else {
461
+ info.call_info_kw_explicit_args = malloc(sizeof(*info.call_info_kw_explicit_args));
462
+ info.call_info_kw_explicit_args[0] = NULL;
279
463
  }
464
+ return info;
280
465
  }
281
466
  }
282
- return info;
467
+ return empty;
283
468
  }
284
469
 
285
470
  static const char*
@@ -327,7 +512,7 @@ fast_join_array(char sep, size_t count, const char **strings)
327
512
  lengths[i + 1] = lengths[i] + length;
328
513
  }
329
514
 
330
- result = (char*)malloc(sizeof(char) * (1 + lengths[count]));
515
+ result = (char *)malloc(sizeof(*result) * (1 + lengths[count]));
331
516
 
332
517
  for (i = 0; i < count; i++)
333
518
  {
@@ -338,7 +523,7 @@ fast_join_array(char sep, size_t count, const char **strings)
338
523
  if (i > 0)
339
524
  result[start++] = sep;
340
525
 
341
- memcpy(result + sizeof(char) * start, str, sizeof(char) * (lengths[i + 1] - start));
526
+ memcpy(result + start, str, sizeof(*result) * (lengths[i + 1] - start));
342
527
  }
343
528
  }
344
529
 
@@ -364,8 +549,72 @@ fast_join(char sep, size_t count, ...)
364
549
  return fast_join_array(sep, count, strings);
365
550
  }
366
551
 
552
+ /**
553
+ * Checks that `container` contains `element`
554
+ */
555
+ static int contains(const char *const *container, const char *element) {
556
+ if (container == NULL || element == NULL) {
557
+ return 0;
558
+ }
559
+ const char *const *iterator = container;
560
+ while (*iterator != NULL) {
561
+ if (strcmp(*iterator, element) == 0) {
562
+ return 1;
563
+ }
564
+ ++iterator;
565
+ }
566
+ return 0;
567
+ }
568
+
569
+ #define JOIN_KW_NAMES_AND_TYPES_BUF_SIZE 2048
570
+ static char join_kw_names_and_types_buf[JOIN_KW_NAMES_AND_TYPES_BUF_SIZE];
571
+ /**
572
+ * Null terminating array which contains strings of explicitly passed kw args.
573
+ * It's used for join_kw_names_and_types
574
+ */
575
+ static const char *const *join_kw_names_and_types_explicit_kw_args = NULL;
576
+
577
+ /**
578
+ * This function is used for concatenating hash keys and value's types.
579
+ * Be sure that buf is at least JOIN_KW_NAMES_AND_TYPES_BUF_SIZE bytes.
580
+ * If join_kw_names_and_types_buf size = JOIN_KW_NAMES_AND_TYPES_BUF_SIZE
581
+ * isn't enough then this buf will contain invalid information
582
+ */
583
+ static int join_kw_names_and_types(VALUE key, VALUE val, VALUE ignored) {
584
+ const char *kw_name = rb_id2name(SYM2ID(key));
585
+ const char *kw_type = calc_sane_class_name(val);
586
+
587
+ const char *const *explicit_kw_args_iterator = join_kw_names_and_types_explicit_kw_args;
588
+
589
+ // Just such behaviour: when join_kw_names_and_types_explicit_kw_args is
590
+ // not provided then consider every kw arg as explicitly passed by user
591
+ int is_explicit = explicit_kw_args_iterator == NULL;
592
+
593
+ if (explicit_kw_args_iterator != NULL) {
594
+ while(*explicit_kw_args_iterator != NULL) {
595
+ if (strcmp(*explicit_kw_args_iterator, kw_name) == 0) {
596
+ is_explicit = 1;
597
+ break;
598
+ }
599
+ ++explicit_kw_args_iterator;
600
+ }
601
+ }
602
+
603
+ if (is_explicit) {
604
+ // Check that buf is not empty
605
+ if (join_kw_names_and_types_buf[0] != '\0') {
606
+ strncat(join_kw_names_and_types_buf, ";", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
607
+ }
608
+ strncat(join_kw_names_and_types_buf, "KEYREST,", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
609
+ strncat(join_kw_names_and_types_buf, kw_type, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
610
+ strncat(join_kw_names_and_types_buf, ",", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
611
+ strncat(join_kw_names_and_types_buf, kw_name, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
612
+ }
613
+ return ST_CONTINUE;
614
+ }
615
+
367
616
  static char*
368
- get_args_info()
617
+ get_args_info(const char *const *explicit_kw_args)
369
618
  {
370
619
  rb_thread_t *thread;
371
620
  rb_control_frame_t *cfp;
@@ -373,7 +622,7 @@ get_args_info()
373
622
  thread = ruby_current_thread;
374
623
  cfp = TH_CFP(thread);
375
624
 
376
- cfp += 3;
625
+ cfp += 2;
377
626
 
378
627
  VALUE *ep = cfp->ep;
379
628
  ep -= cfp->iseq->body->local_table_size;
@@ -398,10 +647,11 @@ get_args_info()
398
647
  LOG("%d\n", has_kwrest);
399
648
  LOG("%d\n", has_block);
400
649
 
401
- if(param_size == 0)
650
+ if (param_size == 0) {
402
651
  return 0;
652
+ }
403
653
 
404
- const char **types = (const char **)malloc(param_size * sizeof(const char*));
654
+ const char **types = (const char **)malloc(param_size * sizeof(*types));
405
655
  size_t i, ans_iterator;
406
656
  int types_iterator;
407
657
 
@@ -412,17 +662,18 @@ get_args_info()
412
662
 
413
663
  for(i = param_size - 1 - new_version_flag, types_iterator = 0; (size_t)types_iterator < param_size; i--, types_iterator++)
414
664
  {
415
- types[types_iterator] = calc_sane_class_name(*(ep + i - 1));
665
+ types[types_iterator] = calc_sane_class_name(ep[i - 1]);
416
666
  types_ids[types_iterator] = i - 1;
417
667
  LOG("Type #%d=%s\n", types_iterator, types[types_iterator])
418
668
  }
419
669
 
420
670
  types_iterator--;
421
671
 
422
- if(has_kw)
672
+ if(has_kw) {
423
673
  param_size--;
674
+ }
424
675
 
425
- char **ans = (char** )malloc(param_size * sizeof(char*));
676
+ char **ans = (char **)malloc(param_size * sizeof(*ans));
426
677
 
427
678
  for(i = 0; i < lead_num; i++, ans_iterator++, types_iterator--)
428
679
  {
@@ -439,11 +690,7 @@ get_args_info()
439
690
  for(i = 0; i < has_rest; i++, ans_iterator++, types_iterator--)
440
691
  {
441
692
  const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
442
-
443
- char* type;
444
- type = types[types_iterator];
445
-
446
- ans[ans_iterator] = fast_join(',', 3, "REST", type, name);
693
+ ans[ans_iterator] = fast_join(',', 3, "REST", types[types_iterator], name);
447
694
  }
448
695
 
449
696
  for(i = 0; i < post_num; i++, ans_iterator++, types_iterator--)
@@ -458,6 +705,7 @@ get_args_info()
458
705
  const ID *keywords = cfp->iseq->body->param.keyword->table;
459
706
  size_t kw_num = cfp->iseq->body->param.keyword->num;
460
707
  size_t required_num = cfp->iseq->body->param.keyword->required_num;
708
+ size_t rest_start = cfp->iseq->body->param.keyword->rest_start;
461
709
 
462
710
  LOG("%d %d\n", kw_num, required_num)
463
711
 
@@ -466,26 +714,39 @@ get_args_info()
466
714
  ID key = keywords[i];
467
715
  ans[ans_iterator] = fast_join(',', 3, "KEYREQ", types[types_iterator], rb_id2name(key));
468
716
  }
469
- for(i = required_num; i < kw_num; i++, ans_iterator++, types_iterator--)
717
+ for(i = required_num; i < kw_num; i++, types_iterator--)
470
718
  {
471
719
  ID key = keywords[i];
472
- ans[ans_iterator] = fast_join(',', 3, "KEY", types[types_iterator], rb_id2name(key));
720
+ const char *name = rb_id2name(key);
721
+ if (explicit_kw_args == NULL || contains(explicit_kw_args, name)) {
722
+ ans[ans_iterator++] = fast_join(',', 3, "KEY", types[types_iterator], name);
723
+ }
473
724
  }
474
- }
475
-
476
- if(param_size - has_block > 1 && has_kwrest && TYPE(*(ep + types_ids[types_iterator])) == T_FIXNUM)
477
- types_iterator--;
478
725
 
479
- for(i = 0; i < has_kwrest; i++, ans_iterator++, types_iterator--)
480
- {
481
- const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
482
- LOG("%s\n", calc_sane_class_name(*(ep + types_ids[types_iterator])));
483
- LOG("%d\n", rb_hash_size(*(ep + types_ids[types_iterator])));
484
- char* type;
485
-
486
- type = types[types_iterator];
726
+ if (param_size - has_block > 1 && has_kwrest && TYPE(ep[types_ids[types_iterator]]) == T_FIXNUM) {
727
+ types_iterator--;
728
+ }
487
729
 
488
- ans[ans_iterator] = fast_join(',', 3, "KEYREST", type, name);
730
+ if (has_kwrest)
731
+ {
732
+ char *buf = malloc(JOIN_KW_NAMES_AND_TYPES_BUF_SIZE * sizeof(*buf));
733
+ buf[0] = '\0';
734
+ join_kw_names_and_types_buf[0] = '\0';
735
+ join_kw_names_and_types_explicit_kw_args = explicit_kw_args;
736
+
737
+ // This function call will concatenate info into join_kw_names_and_types_buf
738
+ rb_hash_foreach(ep[types_ids[types_iterator]], join_kw_names_and_types, Qnil);
739
+
740
+ // Checking that join_kw_names_and_types_buf isn't possibly containing invalid info.
741
+ // See join_kw_names_and_types documentation to understand why it can be invalid
742
+ size_t len = strlen(join_kw_names_and_types_buf);
743
+ if (len > 0 && len < JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1) {
744
+ strncpy(buf, join_kw_names_and_types_buf, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE);
745
+ ans[ans_iterator++] = buf;
746
+ }
747
+ join_kw_names_and_types_explicit_kw_args = NULL;
748
+ types_iterator--;
749
+ }
489
750
  }
490
751
 
491
752
  for(i = 0; i < has_block; i++, ans_iterator++, types_iterator--)
@@ -494,15 +755,6 @@ get_args_info()
494
755
  ans[ans_iterator] = fast_join(',', 3, "BLOCK", types[types_iterator], name);
495
756
  }
496
757
 
497
- int answer_size = 0;
498
-
499
- for(i = 0; i < ans_iterator; i++)
500
- {
501
- answer_size += strlen(ans[i]);
502
- if(i + 1 < ans_iterator)
503
- answer_size++;
504
- }
505
-
506
758
  LOG("%d\n", ans_iterator)
507
759
  char *answer = fast_join_array(';', ans_iterator, ans);
508
760
 
@@ -512,7 +764,6 @@ get_args_info()
512
764
  }
513
765
 
514
766
  LOG("%d %d %d", ans_iterator, param_size, types_iterator);
515
- assert(ans_iterator == param_size);
516
767
  assert(types_iterator <= 0);
517
768
 
518
769
  free(types);
@@ -524,22 +775,42 @@ get_args_info()
524
775
  static VALUE
525
776
  get_args_info_rb(VALUE self)
526
777
  {
527
- char *args_info = get_args_info();
528
- return args_info ? rb_str_new_cstr(args_info) : Qnil;
778
+ call_info_t info;
779
+ info.call_info_kw_explicit_args = NULL;
780
+ if (is_call_info_needed()) {
781
+ info = get_call_info();
782
+ }
783
+
784
+ char *args_info = get_args_info(info.call_info_kw_explicit_args);
785
+ call_info_t_free(info);
786
+ VALUE ret = args_info ? rb_str_new_cstr(args_info) : Qnil;
787
+ free(args_info);
788
+ return ret;
529
789
  }
530
790
 
531
791
  static VALUE
532
792
  get_call_info_rb(VALUE self)
533
793
  {
534
- if(is_call_info_needed())
794
+ if (is_call_info_needed())
535
795
  {
536
- call_info_t *info = get_call_info();
796
+ call_info_t info = get_call_info();
537
797
 
538
798
  VALUE ans;
539
799
  ans = rb_ary_new();
540
- rb_ary_push(ans, LONG2FIX(info->call_info_argc));
541
- if(info->call_info_kw_args != 0)
542
- rb_ary_push(ans, rb_str_new_cstr(info->call_info_kw_args));
800
+ rb_ary_push(ans, LONG2FIX(info.call_info_explicit_argc));
801
+ if (info.call_info_kw_explicit_args != NULL) {
802
+ const char *const *kwarg = info.call_info_kw_explicit_args;
803
+ int explicit_kw_count = 0;
804
+ while (*kwarg != NULL) {
805
+ ++explicit_kw_count;
806
+ ++kwarg;
807
+ }
808
+ char *answer = fast_join_array(',', explicit_kw_count, info.call_info_kw_explicit_args);
809
+ rb_ary_push(ans, rb_str_new_cstr(answer));
810
+ free(answer);
811
+ }
812
+
813
+ call_info_t_free(info);
543
814
 
544
815
  return ans;
545
816
  }
@@ -557,10 +828,30 @@ is_call_info_needed()
557
828
 
558
829
  thread = ruby_current_thread;
559
830
  cfp = TH_CFP(thread);
560
- cfp += 3;
831
+ cfp += 2;
561
832
 
562
833
  return (cfp->iseq->body->param.flags.has_opt
563
834
  || cfp->iseq->body->param.flags.has_kwrest
564
835
  || cfp->iseq->body->param.flags.has_rest
565
836
  || (cfp->iseq->body->param.keyword != NULL && cfp->iseq->body->param.keyword->required_num == 0));
566
- }
837
+ }
838
+
839
+ static VALUE
840
+ check_if_arg_scanner_ready(VALUE self) {
841
+ char error_msg[1024];
842
+ if (pipe_file == NULL) {
843
+ snprintf(error_msg, sizeof(error_msg)/sizeof(*error_msg), "Pipe file is not specified");
844
+ return rb_str_new_cstr(error_msg);
845
+ }
846
+ return Qnil;
847
+ }
848
+
849
+ static VALUE
850
+ destructor(VALUE self) {
851
+ g_tree_destroy(sent_to_server_tree);
852
+ g_tree_destroy(number_missed_calls_tree);
853
+ fprintf(pipe_file, "%s\n", ARG_SCANNER_EXIT_COMMAND);
854
+ fclose(pipe_file);
855
+ free(project_root);
856
+ return Qnil;
857
+ }