arg_scanner 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +22 -0
- data/arg_scanner.gemspec +37 -0
- data/bin/arg-scanner +53 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/arg_scanner/arg_scanner.c +568 -0
- data/ext/arg_scanner/arg_scanner.h +11 -0
- data/ext/arg_scanner/extconf.rb +37 -0
- data/lib/arg_scanner.rb +7 -0
- data/lib/arg_scanner/options.rb +15 -0
- data/lib/arg_scanner/starter.rb +4 -0
- data/lib/arg_scanner/type_tracker.rb +88 -0
- data/lib/arg_scanner/version.rb +3 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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]
|
data/arg_scanner.gemspec
ADDED
@@ -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
|
data/bin/arg-scanner
ADDED
@@ -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
|
data/bin/console
ADDED
@@ -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
|
data/bin/setup
ADDED
@@ -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,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
|
data/lib/arg_scanner.rb
ADDED
@@ -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,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
|
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: []
|