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