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 +5 -5
- data/README.md +6 -1
- data/bin/arg-scanner +17 -17
- data/bin/rubymine-type-tracker +73 -0
- data/ext/arg_scanner/arg_scanner.c +503 -212
- data/ext/arg_scanner/extconf.rb +4 -2
- data/lib/arg_scanner.rb +0 -1
- data/lib/arg_scanner/options.rb +8 -8
- data/lib/arg_scanner/starter.rb +3 -6
- data/lib/arg_scanner/state_tracker.rb +29 -59
- data/lib/arg_scanner/type_tracker.rb +13 -83
- data/lib/arg_scanner/version.rb +1 -1
- data/lib/arg_scanner/workspace.rb +28 -0
- metadata +6 -5
- data/lib/arg_scanner/return_type_tracker.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 630a4491a80316eccf60cecf3306b8a9efe9344d8604e552a39a8a2fd1616790
|
4
|
+
data.tar.gz: 4365cc9d2dfe5a347423dd73c810d60957fec46bd092acda0e3cad08d358a1eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
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.
|
data/bin/arg-scanner
CHANGED
@@ -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
|
-
|
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
|
34
|
-
char
|
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
|
-
|
40
|
-
char*
|
41
|
-
|
42
|
-
char*
|
43
|
-
|
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*
|
50
|
-
static
|
51
|
-
static
|
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
|
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(
|
98
|
+
static void call_info_t_free(call_info_t s)
|
61
99
|
{
|
62
|
-
|
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(
|
103
|
+
static void signature_t_free(signature_t *s)
|
68
104
|
{
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
80
|
-
|
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,
|
86
|
-
rb_define_module_function(mArgScanner, "handle_return", handle_return,
|
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
|
292
|
+
handle_call(VALUE self, VALUE tp)
|
105
293
|
{
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
VALUE
|
110
|
-
|
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
|
-
|
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
|
-
|
129
|
-
|
303
|
+
if (project_root != NULL && !is_in_project_root) {
|
304
|
+
signature_t *peek = top_of_call_stack();
|
130
305
|
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
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
|
-
|
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",
|
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
|
-
|
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
|
-
|
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
|
-
|
348
|
+
push_to_call_stack(sign);
|
349
|
+
return Qnil;
|
185
350
|
}
|
186
351
|
|
187
|
-
static
|
188
|
-
|
352
|
+
static VALUE
|
353
|
+
handle_return(VALUE self, VALUE tp)
|
189
354
|
{
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
201
|
-
info->call_info_kw_args = 0;
|
361
|
+
VALUE receiver_name = rb_mod_name(defined_class);
|
202
362
|
|
203
|
-
|
204
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
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
|
-
|
413
|
+
call_info_t empty;
|
414
|
+
empty.call_info_kw_explicit_args = NULL;
|
415
|
+
empty.call_info_explicit_argc = -1;
|
235
416
|
|
236
|
-
|
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
|
-
|
420
|
+
if(cfp->iseq == NULL || cfp->pc == NULL || cfp->iseq->body == NULL) {
|
421
|
+
return empty;
|
422
|
+
}
|
241
423
|
|
242
|
-
|
243
|
-
const char *c_kw_ary[kwArgSize];
|
424
|
+
const rb_iseq_t *iseq = (const rb_iseq_t *) cfp->iseq;
|
244
425
|
|
245
|
-
|
246
|
-
int j;
|
426
|
+
ptrdiff_t pc = cfp->pc - cfp->iseq->body->iseq_encoded;
|
247
427
|
|
248
|
-
|
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
|
-
|
254
|
-
|
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
|
-
|
257
|
-
|
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
|
-
|
446
|
+
size_t kwArgSize = kw_args->keyword_len;
|
261
447
|
|
262
|
-
|
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
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
strcat(info->call_info_kw_args, ",");
|
276
|
-
}
|
457
|
+
info.call_info_kw_explicit_args[i] = kw_name;
|
277
458
|
}
|
278
|
-
|
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
|
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(
|
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 +
|
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 +=
|
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(
|
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(
|
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**
|
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++,
|
717
|
+
for(i = required_num; i < kw_num; i++, types_iterator--)
|
470
718
|
{
|
471
719
|
ID key = keywords[i];
|
472
|
-
|
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
|
-
|
480
|
-
|
481
|
-
|
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
|
-
|
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
|
-
|
528
|
-
|
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
|
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
|
541
|
-
if(info
|
542
|
-
|
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 +=
|
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
|
+
}
|