arg_scanner 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []