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.
- 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: []
|