arg_scanner 0.2.0 → 0.3.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 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
+ }