arg_scanner 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7a5018e4c54e0aac945b4eb3d1d19c0915109516
4
+ data.tar.gz: 97a6c8b858413da9a447a4d34d7310ad9ab97f3b
5
+ SHA512:
6
+ metadata.gz: d2b050da192b7def101aea6f66c8ac2fa6cb5e507fe01579bc96bcb129a64e04b8c272fcf1057b197aa53016b1527b2a10f32b5525573488e7ef92bf7d14b2f3
7
+ data.tar.gz: 55d111320901c78a7a64a71c39c2ba2a991543db1993a5b6b795fd8adf2900202fd9297465829c82ec2b0ae559c9b21f9abca935a9d0992dd8cf580eca250882
@@ -0,0 +1,16 @@
1
+ *.iml
2
+
3
+ .bundle/
4
+ .yardoc
5
+ Gemfile.lock
6
+ _yardoc/
7
+ coverage/
8
+ doc/
9
+ pkg/
10
+ spec/reports/
11
+ tmp/
12
+ *.bundle
13
+ *.so
14
+ *.o
15
+ *.a
16
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in arg_scanner.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'test-unit'
8
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 JetBrains
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # ArgScanner
2
+
3
+ `arg_scanner` is a gem with the purpose to track all method calls and
4
+ deliver the following information:
5
+
6
+ * Method signature (arguments, their names and kinds) and declaration place
7
+ * The types of argument variables given to each method call done
8
+
9
+ This information can be used then to calculate and use type contracts
10
+ for the analysed methods.
11
+
12
+ ## Installation
13
+
14
+
15
+ `arg_scanner` is meant to be used as a binary to run any other ruby executable
16
+ manually so including it in the `Gemfile` is not necessary.
17
+
18
+ The recommended way to install it is to install manually:
19
+
20
+ $ gem install arg_scanner
21
+
22
+ If you want to compile the gem from sources, just run
23
+
24
+ $ bundle install
25
+ $ bundle exec rake install
26
+
27
+ If you have problems with native extension compilation, make sure you have
28
+ actual version of [ruby-core-source gem](https://github.com/os97673/debase-ruby_core_source).
29
+
30
+ ## Usage
31
+
32
+ `arg_scanner` provides binary `arg-scanner` which receives any number of
33
+ arguments and executes the given command in type tracking mode,
34
+ for example:
35
+
36
+ $ arg-scanner bundle exec rake spec
37
+
38
+ The gem will need to send the obtained data though TCP socket on **port 7777**.
39
+ See [global readme](../README.md) for instructions on how to run server
40
+ to receive and process that data.
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JetBrains/ruby-type-inference
45
+
46
+ ## License
47
+
48
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
49
+
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+ require 'rake/testtask'
4
+
5
+ BASE_TEST_FILE_LIST = Dir['test/**/test_*.rb']
6
+
7
+ task :build => :compile
8
+
9
+ Rake::ExtensionTask.new("arg_scanner") do |ext|
10
+ ext.lib_dir = "lib/arg_scanner"
11
+ end
12
+
13
+ desc "Test arg_scanner."
14
+ Rake::TestTask.new(:test => [:clean, :compile]) do |t|
15
+ t.libs += %w(./ext ./lib)
16
+ t.test_files = FileList[BASE_TEST_FILE_LIST]
17
+ t.verbose = true
18
+ end
19
+
20
+ task :test => :lib
21
+
22
+ task :default => [:clobber, :compile, :test]
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'arg_scanner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "arg_scanner"
8
+ spec.version = ArgScanner::VERSION
9
+ spec.authors = ["Nickolay Viuginov", "Valentin Fondaratov"]
10
+ spec.email = ["viuginov.nickolay@gmail.com", "fondarat@gmail.com"]
11
+
12
+ spec.summary = %q{Program execution tracker to retrieve data types information}
13
+ spec.homepage = "https://github.com/jetbrains/ruby-type-inference"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ # if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
20
+ # else
21
+ # raise "RubyGems 2.0 or newer is required to protect against " \
22
+ # "public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "bin"
29
+ spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f)}
30
+ spec.require_paths = ["lib"]
31
+ spec.extensions = ["ext/arg_scanner/extconf.rb"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.13"
34
+ spec.add_development_dependency "rake", "~> 12.0"
35
+ spec.add_development_dependency "rake-compiler"
36
+ spec.add_development_dependency "debase-ruby_core_source", "~> 0.9.6"
37
+ end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'arg_scanner/options'
5
+
6
+ options = ArgScanner::OPTIONS
7
+ option_parser = OptionParser.new do |opts|
8
+ opts.banner = <<~EOB
9
+ Usage: arg-scanner [OPTIONS] <ruby cmdline>
10
+ arg-scanner is a ruby script mediator supposed to be run from the command line or IDE.
11
+ The data will be sent to a signature server so it must be running during arg-scanner execution.
12
+ EOB
13
+
14
+ opts.separator "Options:"
15
+ opts.on("-r", "--root=[ROOT]", String, "local project root(s) to distinguish from library sources (path1[:pathn]*)") do |paths|
16
+ options.project_roots = paths.split ':' if paths
17
+ end
18
+ opts.on("--local=[VERSION]", Integer,
19
+ "local source treatment: mark as fake gem with given VERSION, default: 0") do |local|
20
+ options.local_version = local.to_s
21
+ end
22
+ opts.on("--no-local", "local source treatment: ignore, do not send data from local sources") do
23
+ options.no_local = true
24
+ end
25
+ end
26
+
27
+ begin
28
+ option_parser.parse! ARGV
29
+ rescue StandardError => e
30
+ puts option_parser
31
+ puts
32
+ puts e.message
33
+ exit 1
34
+ end
35
+
36
+ if ARGV.size < 1
37
+ puts option_parser
38
+ puts
39
+ puts "Ruby program to trace must be specified."
40
+ exit 1
41
+ end
42
+
43
+ options.set_env
44
+
45
+ old_opts = ENV['RUBYOPT'] || ''
46
+ starter = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/arg_scanner/starter"
47
+ unless old_opts.include? starter
48
+ ENV['RUBYOPT'] = starter
49
+ ENV['RUBYOPT'] += " #{old_opts}" if old_opts != ''
50
+ end
51
+
52
+ $0 = ARGV[0]
53
+ Kernel.exec *ARGV
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "arg_scanner"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,568 @@
1
+ #include "arg_scanner.h"
2
+ #include <stdbool.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <assert.h>
7
+ #include <stdarg.h>
8
+
9
+ //#define DEBUG_ARG_SCANNER 1
10
+
11
+ #ifdef DEBUG_ARG_SCANNER
12
+ #define LOG(f, args...) { fprintf(stderr, "DEBUG: '%s'=", #args); fprintf(stderr, f, ##args); fflush(stderr); }
13
+ #else
14
+ #define LOG(...) {}
15
+ #endif
16
+
17
+ #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
18
+ typedef struct rb_trace_arg_struct rb_trace_arg_t;
19
+
20
+ VALUE mArgScanner = Qnil;
21
+ int types_ids[20];
22
+
23
+ static VALUE c_signature;
24
+
25
+ typedef struct
26
+ {
27
+ ssize_t call_info_argc;
28
+ char* call_info_kw_args;
29
+ } call_info_t;
30
+
31
+ typedef struct
32
+ {
33
+ VALUE method_name;
34
+ char* args_info;
35
+ VALUE path;
36
+ char* call_info_kw_args;
37
+ ssize_t call_info_argc;
38
+ int lineno;
39
+ } signature_t;
40
+
41
+ void Init_arg_scanner();
42
+
43
+ static char* get_args_info();
44
+ static VALUE handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path);
45
+ static VALUE handle_return(VALUE self, VALUE signature, VALUE return_type_name);
46
+
47
+ // For testing
48
+ static VALUE get_args_info_rb(VALUE self);
49
+ static VALUE get_call_info_rb(VALUE self);
50
+
51
+ static call_info_t* get_call_info();
52
+ static bool is_call_info_needed();
53
+
54
+ static void call_info_t_free(void *s)
55
+ {
56
+ if (((call_info_t *)s)->call_info_kw_args != 0)
57
+ free(((call_info_t *)s)->call_info_kw_args);
58
+ free(s);
59
+ }
60
+
61
+ static void signature_t_free(void *s)
62
+ {
63
+ if (((signature_t *)s)->args_info != 0)
64
+ free(((signature_t *)s)->args_info);
65
+ if (((signature_t *)s)->call_info_kw_args != 0)
66
+ free(((signature_t *)s)->call_info_kw_args);
67
+ free(s);
68
+ }
69
+
70
+ static void
71
+ signature_t_mark(signature_t *sig)
72
+ {
73
+ rb_gc_mark(sig->method_name);
74
+ rb_gc_mark(sig->path);
75
+ }
76
+
77
+ void Init_arg_scanner() {
78
+ mArgScanner = rb_define_module("ArgScanner");
79
+ rb_define_module_function(mArgScanner, "handle_call", handle_call, 3);
80
+ rb_define_module_function(mArgScanner, "handle_return", handle_return, 2);
81
+ rb_define_module_function(mArgScanner, "get_args_info", get_args_info_rb, 0);
82
+ rb_define_module_function(mArgScanner, "get_call_info", get_call_info_rb, 0);
83
+ }
84
+
85
+ rb_control_frame_t *
86
+ my_rb_vm_get_binding_creatable_next_cfp(const rb_thread_t *th, const rb_control_frame_t *cfp)
87
+ {
88
+ while (!RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
89
+ if (cfp->iseq) {
90
+ return (rb_control_frame_t *)cfp;
91
+ }
92
+ cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
93
+ }
94
+ return 0;
95
+ }
96
+
97
+ static VALUE
98
+ handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path)
99
+ {
100
+ //VALUE method_sym = rb_tracearg_method_id(trace_arg);
101
+ //VALUE path = trace_arg->path;
102
+ VALUE c_method_name = method_name;
103
+ VALUE c_path = path;
104
+ int c_lineno = FIX2INT(lineno);//trace_arg->lineno;
105
+
106
+ signature_t *sign;
107
+ sign = ALLOC(signature_t);
108
+
109
+ sign->lineno = c_lineno;
110
+ sign->method_name = c_method_name;
111
+ sign->path = c_path;
112
+
113
+ #ifdef DEBUG_ARG_SCANNER
114
+ LOG("Getting args info for %s %s %d \n", rb_id2name(SYM2ID(sign->method_name)), StringValuePtr(sign->path), sign->lineno);
115
+ #endif
116
+ sign->args_info = get_args_info();
117
+
118
+ if (is_call_info_needed())
119
+ {
120
+ call_info_t *info = get_call_info();
121
+
122
+ sign->call_info_argc = info->call_info_argc;
123
+ sign->call_info_kw_args = info->call_info_kw_args;
124
+
125
+ free(info);
126
+ } else {
127
+ sign->call_info_argc = -1;
128
+ sign->call_info_kw_args = 0;
129
+ }
130
+
131
+ //return Data_Wrap_Struct(c_signature, signature_t_mark, signature_t_free, sign);
132
+ return Data_Wrap_Struct(c_signature, signature_t_mark, xfree, sign);
133
+ }
134
+
135
+ static VALUE
136
+ handle_return(VALUE self, VALUE signature, VALUE return_type_name)
137
+ {
138
+ signature_t *sign;
139
+ const char *args_info;
140
+ const char *call_info_kw_args;
141
+ char json_mes[2000];
142
+
143
+ Data_Get_Struct(signature, signature_t, sign);
144
+
145
+ args_info = sign->args_info;
146
+ if (!args_info)
147
+ args_info = "";
148
+ call_info_kw_args = sign->call_info_kw_args;
149
+ if (!call_info_kw_args)
150
+ call_info_kw_args = "";
151
+
152
+
153
+ #ifdef DEBUG_ARG_SCANNER
154
+ LOG("%s \n", rb_id2name(SYM2ID(sign->method_name)));
155
+ LOG("%d \n", sign->call_info_argc);
156
+ LOG("%s \n", call_info_kw_args);
157
+ LOG("%s \n", args_info);
158
+ LOG("%s \n", StringValuePtr(sign->path));
159
+ LOG("%d \n", sign->lineno);
160
+ #endif
161
+
162
+ assert(strlen(args_info) < 1000);
163
+
164
+ snprintf(json_mes, 2000,
165
+ "{\"method_name\":\"%s\",\"call_info_argc\":\"%d\",\"call_info_kw_args\":\"%s\",\"args_info\":\"%s\",\"visibility\":\"%s\",\"path\":\"%s\",\"lineno\":\"%d\",",
166
+ rb_id2name(SYM2ID(sign->method_name)),
167
+ sign->call_info_argc,
168
+ call_info_kw_args,
169
+ //StringValuePtr(receiver_name),
170
+ args_info,
171
+ //StringValuePtr(return_type_name),
172
+ "PUBLIC",
173
+ StringValuePtr(sign->path),
174
+ sign->lineno);
175
+
176
+ LOG("%s \n", json_mes);
177
+
178
+ return rb_str_new_cstr(json_mes);
179
+ }
180
+
181
+ static call_info_t*
182
+ get_call_info()
183
+ {
184
+ rb_thread_t *thread;
185
+ rb_control_frame_t *cfp;
186
+ call_info_t *info;
187
+
188
+ thread = ruby_current_thread;
189
+ cfp = thread->cfp;
190
+ info = malloc(sizeof(call_info_t));
191
+ //info = ALLOC(call_info_t);
192
+ //info = malloc;
193
+
194
+ info->call_info_argc = -1;
195
+ info->call_info_kw_args = 0;
196
+
197
+ cfp += 4;
198
+ cfp = my_rb_vm_get_binding_creatable_next_cfp(thread, cfp);
199
+
200
+ if(cfp->iseq != NULL)
201
+ {
202
+ if(cfp->pc == NULL || cfp->iseq->body == NULL)
203
+ {
204
+ return info;
205
+ }
206
+
207
+ const rb_iseq_t *iseq = cfp->iseq;
208
+
209
+ ptrdiff_t pc = cfp->pc - cfp->iseq->body->iseq_encoded;
210
+
211
+ const VALUE *iseq_original = rb_iseq_original_iseq((rb_iseq_t *)iseq);
212
+
213
+ int tmp = 0;
214
+ int indent = 1;
215
+
216
+ for(; indent < 6; indent++)
217
+ {
218
+ VALUE insn = iseq_original[pc - indent];
219
+ tmp = (int)insn;
220
+
221
+ if(0 < tmp && tmp < 256)
222
+ {
223
+ if(indent < 3)
224
+ return info;
225
+
226
+ struct rb_call_info *ci = (struct rb_call_info *)iseq_original[pc - indent + 1];
227
+
228
+ info->call_info_argc = ci->orig_argc;
229
+
230
+ if (ci->flag & VM_CALL_KWARG)
231
+ {
232
+ struct rb_call_info_kw_arg *kw_args = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
233
+
234
+ size_t kwArgSize = kw_args->keyword_len;
235
+
236
+ VALUE kw_ary = rb_ary_new_from_values(kw_args->keyword_len, kw_args->keywords);
237
+ const char *c_kw_ary[kwArgSize];
238
+
239
+ size_t ans_size = 0;
240
+ int j;
241
+
242
+ for(j = kwArgSize - 1; j >= 0; j--)
243
+ {
244
+ VALUE kw = rb_ary_pop(kw_ary);
245
+ const char* kw_name = rb_id2name(SYM2ID(kw));
246
+
247
+ c_kw_ary[j] = kw_name;
248
+ ans_size += strlen(kw_name);
249
+
250
+ if((size_t)j + 1 < kwArgSize)
251
+ ans_size++;
252
+ }
253
+
254
+ info->call_info_kw_args = (char*)malloc(ans_size + 1);
255
+
256
+ if(kwArgSize > 0)
257
+ {
258
+ strcpy(info->call_info_kw_args, c_kw_ary[0]);
259
+
260
+ if(kwArgSize > 1)
261
+ strcat(info->call_info_kw_args, ",");
262
+ }
263
+
264
+ for(j = 1; (size_t)j < kwArgSize; j++)
265
+ {
266
+ strcat(info->call_info_kw_args, c_kw_ary[j]);
267
+
268
+ if((size_t)j + 1 < kwArgSize)
269
+ strcat(info->call_info_kw_args, ",");
270
+ }
271
+ }
272
+ return info;
273
+ }
274
+ }
275
+ }
276
+ return info;
277
+ }
278
+
279
+ static const char*
280
+ calc_sane_class_name(VALUE ptr)
281
+ {
282
+ VALUE klass = rb_obj_class(ptr);
283
+
284
+ const char* klass_name;
285
+ // may be false, see `object.c#rb_class_get_superclass`
286
+ if (klass == Qfalse) {
287
+ klass_name = "<err>";
288
+ }
289
+ else
290
+ {
291
+ klass_name = rb_class2name(klass);
292
+ }
293
+
294
+ // returned value may be NULL, see `variable.c#rb_class2name`
295
+ if (klass_name == NULL)
296
+ {
297
+ klass_name = "<err>";
298
+ }
299
+
300
+ if (strlen(klass_name) >= 200)
301
+ {
302
+ fprintf(stderr, "ERROR: too long class name: '%s'\n", klass_name);
303
+ assert(false);
304
+ }
305
+ return klass_name;
306
+ }
307
+
308
+ static char *
309
+ fast_join_array(char sep, size_t count, const char **strings)
310
+ {
311
+ size_t lengths[count + 1];
312
+ size_t i;
313
+ char *result;
314
+
315
+ lengths[0] = 0;
316
+
317
+ for (i = 0; i < count; i++)
318
+ {
319
+ const char *str = strings[i];
320
+ size_t length;
321
+ if (!str)
322
+ length = 0;
323
+ else
324
+ length = strlen(str) + (i > 0); // 1 for separator before
325
+
326
+ lengths[i + 1] = lengths[i] + length;
327
+ }
328
+
329
+ result = (char*)malloc(sizeof(char) * (1 + lengths[count]));
330
+
331
+ for (i = 0; i < count; i++)
332
+ {
333
+ const char *str = strings[i];
334
+ if (str)
335
+ {
336
+ int start = lengths[i];
337
+ if (i > 0)
338
+ result[start++] = sep;
339
+
340
+ memcpy(result + sizeof(char) * start, str, sizeof(char) * (lengths[i + 1] - start));
341
+ }
342
+ }
343
+
344
+ result[lengths[count]] = 0;
345
+
346
+ return result;
347
+ }
348
+
349
+ static char *
350
+ fast_join(char sep, size_t count, ...)
351
+ {
352
+ char *strings[count];
353
+ size_t i;
354
+ va_list ap;
355
+
356
+ va_start(ap, count);
357
+ for (i = 0; i < count; i++)
358
+ {
359
+ strings[i] = va_arg(ap, char *);
360
+ }
361
+ va_end(ap);
362
+
363
+ return fast_join_array(sep, count, strings);
364
+ }
365
+
366
+ static char*
367
+ get_args_info()
368
+ {
369
+ rb_thread_t *thread;
370
+ rb_control_frame_t *cfp;
371
+
372
+ thread = ruby_current_thread;
373
+ cfp = thread->cfp;
374
+
375
+ cfp += 3;
376
+
377
+ VALUE *ep = cfp->ep;
378
+ ep -= cfp->iseq->body->local_table_size;
379
+
380
+ size_t param_size = cfp->iseq->body->param.size;
381
+ size_t lead_num = cfp->iseq->body->param.lead_num;
382
+ size_t opt_num = cfp->iseq->body->param.opt_num;
383
+ size_t post_num = cfp->iseq->body->param.post_num;
384
+
385
+ unsigned int has_rest = cfp->iseq->body->param.flags.has_rest;
386
+ unsigned int has_kw = cfp->iseq->body->param.flags.has_kw;
387
+ unsigned int has_kwrest = cfp->iseq->body->param.flags.has_kwrest;
388
+ unsigned int has_block = cfp->iseq->body->param.flags.has_block;
389
+
390
+ LOG("%d\n", param_size);
391
+ LOG("%d\n", lead_num);
392
+ LOG("%d\n", opt_num);
393
+ LOG("%d\n", post_num);
394
+
395
+ LOG("%d\n", has_rest);
396
+ LOG("%d\n", has_kw);
397
+ LOG("%d\n", has_kwrest);
398
+ LOG("%d\n", has_block);
399
+
400
+ if(param_size == 0)
401
+ return 0;
402
+
403
+ const char **types = (const char **)malloc(param_size * sizeof(const char*));
404
+ size_t i, ans_iterator;
405
+ int types_iterator;
406
+
407
+ ans_iterator = 0;
408
+
409
+ for(i = param_size - 1 - (RUBY_VERSION >= "2.4.1"), types_iterator = 0; (size_t)types_iterator < param_size; i--, types_iterator++)
410
+ {
411
+ types[types_iterator] = calc_sane_class_name(*(ep + i - 1));
412
+ types_ids[types_iterator] = i - 1;
413
+ LOG("Type #%d=%s\n", types_iterator, types[types_iterator])
414
+ }
415
+
416
+ types_iterator--;
417
+
418
+ if(has_kw)
419
+ param_size--;
420
+
421
+ char **ans = (char** )malloc(param_size * sizeof(char*));
422
+
423
+ for(i = 0; i < lead_num; i++, ans_iterator++, types_iterator--)
424
+ {
425
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
426
+ ans[ans_iterator] = fast_join(',', 3, "REQ", types[types_iterator], name);
427
+ }
428
+
429
+ for(i = 0; i < opt_num; i++, ans_iterator++, types_iterator--)
430
+ {
431
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
432
+ ans[ans_iterator] = fast_join(',', 3, "OPT", types[types_iterator], name);
433
+ }
434
+
435
+ for(i = 0; i < has_rest; i++, ans_iterator++, types_iterator--)
436
+ {
437
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
438
+
439
+ char* type;
440
+ if(RARRAY_LEN(*(ep + types_ids[types_iterator])) == 0)
441
+ type = "Array/empty";
442
+ else
443
+ type = types[types_iterator];
444
+
445
+ ans[ans_iterator] = fast_join(',', 3, "REST", type, name);
446
+ }
447
+
448
+ for(i = 0; i < post_num; i++, ans_iterator++, types_iterator--)
449
+ {
450
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
451
+ ans[ans_iterator] = fast_join(',', 3, "POST", types[types_iterator], name);
452
+ }
453
+
454
+
455
+ if(cfp->iseq->body->param.keyword != NULL)
456
+ {
457
+ const ID *keywords = cfp->iseq->body->param.keyword->table;
458
+ size_t kw_num = cfp->iseq->body->param.keyword->num;
459
+ size_t required_num = cfp->iseq->body->param.keyword->required_num;
460
+
461
+ LOG("%d %d\n", kw_num, required_num)
462
+
463
+ for(i = 0; i < required_num; i++, ans_iterator++, types_iterator--)
464
+ {
465
+ ID key = keywords[i];
466
+ ans[ans_iterator] = fast_join(',', 3, "KEYREQ", types[types_iterator], rb_id2name(key));
467
+ }
468
+ for(i = required_num; i < kw_num; i++, ans_iterator++, types_iterator--)
469
+ {
470
+ ID key = keywords[i];
471
+ ans[ans_iterator] = fast_join(',', 3, "KEY", types[types_iterator], rb_id2name(key));
472
+ }
473
+ }
474
+
475
+ if(param_size - has_block > 1 && has_kwrest)
476
+ types_iterator--;
477
+
478
+ for(i = 0; i < has_kwrest; i++, ans_iterator++, types_iterator--)
479
+ {
480
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
481
+ LOG("%s\n", calc_sane_class_name(*(ep + types_ids[types_iterator])));
482
+ LOG("%d\n", rb_hash_size(*(ep + types_ids[types_iterator])));
483
+ char* type;
484
+
485
+ if(rb_hash_size(*(ep + types_ids[types_iterator])) == 1)
486
+ type = "Hash/empty";
487
+ else
488
+ type = types[types_iterator];
489
+
490
+ ans[ans_iterator] = fast_join(',', 3, "KEYREST", type, name);
491
+ }
492
+
493
+ for(i = 0; i < has_block; i++, ans_iterator++, types_iterator--)
494
+ {
495
+ const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
496
+ ans[ans_iterator] = fast_join(',', 3, "BLOCK", types[types_iterator], name);
497
+ }
498
+
499
+ int answer_size = 0;
500
+
501
+ for(i = 0; i < ans_iterator; i++)
502
+ {
503
+ answer_size += strlen(ans[i]);
504
+ if(i + 1 < ans_iterator)
505
+ answer_size++;
506
+ }
507
+
508
+ LOG("%d\n", ans_iterator)
509
+ char *answer = fast_join_array(';', ans_iterator, ans);
510
+
511
+ for(i = 0; i < ans_iterator; i++) {
512
+ LOG("free2 %d %d =%s= \n", ans[i], strlen(ans[i]), ans[i]);
513
+ free(ans[i]);
514
+ }
515
+
516
+ LOG("%d %d %d", ans_iterator, param_size, types_iterator);
517
+ assert(ans_iterator == param_size);
518
+ assert(types_iterator <= 0);
519
+
520
+ free(types);
521
+ free(ans);
522
+
523
+ return answer;
524
+ }
525
+
526
+ static VALUE
527
+ get_args_info_rb(VALUE self)
528
+ {
529
+ char *args_info = get_args_info();
530
+ return args_info ? rb_str_new_cstr(args_info) : Qnil;
531
+ }
532
+
533
+ static VALUE
534
+ get_call_info_rb(VALUE self)
535
+ {
536
+ if(is_call_info_needed())
537
+ {
538
+ call_info_t *info = get_call_info();
539
+
540
+ VALUE ans;
541
+ ans = rb_ary_new();
542
+ rb_ary_push(ans, LONG2FIX(info->call_info_argc));
543
+ if(info->call_info_kw_args != 0)
544
+ rb_ary_push(ans, rb_str_new_cstr(info->call_info_kw_args));
545
+
546
+ return ans;
547
+ }
548
+ else
549
+ {
550
+ return Qnil;
551
+ }
552
+ }
553
+
554
+ static bool
555
+ is_call_info_needed()
556
+ {
557
+ rb_thread_t *thread;
558
+ rb_control_frame_t *cfp;
559
+
560
+ thread = ruby_current_thread;
561
+ cfp = thread->cfp;
562
+ cfp += 3;
563
+
564
+ return (cfp->iseq->body->param.flags.has_opt
565
+ || cfp->iseq->body->param.flags.has_kwrest
566
+ || cfp->iseq->body->param.flags.has_rest
567
+ || (cfp->iseq->body->param.keyword != NULL && cfp->iseq->body->param.keyword->required_num == 0));
568
+ }
@@ -0,0 +1,11 @@
1
+ #ifndef ARG_SCANNER_H
2
+ #define ARG_SCANNER_H 1
3
+
4
+ #include "ruby.h"
5
+ #include "vm_core.h"
6
+ #include "version.h"
7
+ #include "iseq.h"
8
+ #include "vm_insnhelper.h"
9
+ #include "method.h"
10
+
11
+ #endif /* ARG_SCANNER_H */
@@ -0,0 +1,37 @@
1
+ require "mkmf"
2
+
3
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
4
+
5
+ require "debase/ruby_core_source"
6
+
7
+ hdrs = proc {
8
+ have_header("vm_core.h") and
9
+ have_header("iseq.h") and
10
+ have_header("version.h") and
11
+ have_header("vm_core.h") and
12
+ have_header("vm_insnhelper.h") and
13
+ have_header("vm_core.h") and
14
+ have_header("method.h")
15
+ }
16
+
17
+ # Allow use customization of compile options. For example, the
18
+ # following lines could be put in config_options to to turn off
19
+ # optimization:
20
+ # $CFLAGS='-fPIC -fno-strict-aliasing -g3 -ggdb -O2 -fPIC'
21
+ config_file = File.join(File.dirname(__FILE__), 'config_options.rb')
22
+ load config_file if File.exist?(config_file)
23
+
24
+ if ENV['debase_debug']
25
+ $CFLAGS+=' -Wall -Werror'
26
+ $CFLAGS+=' -g3'
27
+ end
28
+
29
+ dir_config("ruby")
30
+ if !Debase::RubyCoreSource.create_makefile_with_core(hdrs, "arg_scanner/arg_scanner")
31
+ STDERR.print("Makefile creation failed\n")
32
+ STDERR.print("*************************************************************\n\n")
33
+ STDERR.print(" NOTE: If your headers were not found, try passing\n")
34
+ STDERR.print(" --with-ruby-include=PATH_TO_HEADERS \n\n")
35
+ STDERR.print("*************************************************************\n\n")
36
+ exit(1)
37
+ end
@@ -0,0 +1,7 @@
1
+ require "arg_scanner/version"
2
+ require "arg_scanner/arg_scanner"
3
+ require "arg_scanner/type_tracker"
4
+
5
+ module ArgScanner
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,15 @@
1
+ require 'ostruct'
2
+
3
+ module ArgScanner
4
+ OPTIONS = OpenStruct.new(
5
+ :local_version => ENV['ARG_SCANNER_LOCAL_VERSION'] || '0',
6
+ :no_local => ENV['ARG_SCANNER_NO_LOCAL'] ? true : false,
7
+ :project_roots => ((ENV['ARG_SCANNER_PROJECT_ROOTS'] || "").split ':')
8
+ )
9
+
10
+ def OPTIONS.set_env
11
+ ENV['ARG_SCANNER_LOCAL_VERSION'] = self.local_version.to_s
12
+ ENV['ARG_SCANNER_NO_LOCAL'] = self.no_local ? "1" : nil
13
+ ENV['ARG_SCANNER_PROJECT_ROOTS'] = self.project_roots.join ':'
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ require 'arg_scanner'
2
+
3
+ # instantiating type tracker will enable calls tracing and sending the data
4
+ ArgScanner::TypeTracker.instance
@@ -0,0 +1,88 @@
1
+ require 'set'
2
+ require 'socket'
3
+ require 'singleton'
4
+
5
+ require_relative 'options'
6
+
7
+ module ArgScanner
8
+ class TypeTracker
9
+ include Singleton
10
+
11
+ GEM_PATH_REVERSED_REGEX = /((?:[0-9A-Za-z]+\.)+\d+)-([A-Za-z0-9_-]+)/
12
+
13
+ def initialize
14
+ @cache = Set.new
15
+ @socket = TCPSocket.new('127.0.0.1', 7777)
16
+
17
+ TracePoint.trace(:call, :return) do |tp|
18
+ case tp.event
19
+ when :call
20
+ handle_call(tp)
21
+ when :return
22
+ handle_return(tp)
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_accessor :cache
28
+ attr_accessor :socket
29
+
30
+
31
+ # @param [String] path
32
+ def self.extract_gem_name_and_version(path)
33
+ if OPTIONS.project_roots && path.start_with?(*OPTIONS.project_roots)
34
+ return OPTIONS.no_local ? ['', ''] : ['LOCAL', OPTIONS.local_version]
35
+ end
36
+
37
+ reversed = path.reverse
38
+ return ['', ''] unless GEM_PATH_REVERSED_REGEX =~ reversed
39
+
40
+ name_and_version = Regexp.last_match
41
+ return name_and_version[2].reverse, name_and_version[1].reverse
42
+ end
43
+
44
+ def signatures
45
+ Thread.current[:signatures] ||= Array.new
46
+ end
47
+
48
+ def at_exit
49
+ socket.close
50
+ end
51
+
52
+ def put_to_socket(message)
53
+ socket.puts(message)
54
+ end
55
+
56
+ private
57
+ def handle_call(tp)
58
+ #handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path)
59
+ signature = ArgScanner.handle_call(tp.lineno, tp.method_id, tp.path)
60
+ signatures.push(signature)
61
+ end
62
+
63
+ def handle_return(tp)
64
+ sigi = signatures
65
+ unless sigi.empty?
66
+ signature = sigi.pop
67
+
68
+ defined_class = tp.defined_class
69
+ return if !defined_class || defined_class.singleton_class?
70
+
71
+ receiver_name = defined_class.name ? defined_class : defined_class.ancestors.first
72
+ return_type_name = tp.return_value.class
73
+
74
+ return if !receiver_name || !receiver_name.to_s || receiver_name.to_s.length > 200
75
+
76
+ json = ArgScanner.handle_return(signature, return_type_name) +
77
+ "\"receiver_name\":\"#{receiver_name}\",\"return_type_name\":\"#{return_type_name}\","
78
+
79
+ if cache.add?(json)
80
+ gem_name, gem_version = TypeTracker.extract_gem_name_and_version(tp.path)
81
+ json += '"gem_name":"' + gem_name.to_s + '","gem_version":"' + gem_version.to_s + '"}'
82
+ put_to_socket(json)
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ module ArgScanner
2
+ VERSION = "0.1.9"
3
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arg_scanner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Nickolay Viuginov
8
+ - Valentin Fondaratov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-09-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.13'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.13'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '12.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '12.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake-compiler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: debase-ruby_core_source
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.9.6
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.9.6
70
+ description:
71
+ email:
72
+ - viuginov.nickolay@gmail.com
73
+ - fondarat@gmail.com
74
+ executables:
75
+ - arg-scanner
76
+ - console
77
+ - setup
78
+ extensions:
79
+ - ext/arg_scanner/extconf.rb
80
+ extra_rdoc_files: []
81
+ files:
82
+ - ".gitignore"
83
+ - Gemfile
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - arg_scanner.gemspec
88
+ - arg_scanner.iml
89
+ - bin/arg-scanner
90
+ - bin/console
91
+ - bin/setup
92
+ - ext/arg_scanner/arg_scanner.c
93
+ - ext/arg_scanner/arg_scanner.h
94
+ - ext/arg_scanner/extconf.rb
95
+ - lib/arg_scanner.rb
96
+ - lib/arg_scanner/options.rb
97
+ - lib/arg_scanner/starter.rb
98
+ - lib/arg_scanner/type_tracker.rb
99
+ - lib/arg_scanner/version.rb
100
+ homepage: https://github.com/jetbrains/ruby-type-inference
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.6.12
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Program execution tracker to retrieve data types information
124
+ test_files: []