arg_scanner 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/arg_scanner.gemspec +3 -3
- data/bin/arg-scanner +27 -1
- data/ext/arg_scanner/arg_scanner.c +16 -18
- data/lib/arg_scanner.rb +2 -0
- data/lib/arg_scanner/options.rb +9 -1
- data/lib/arg_scanner/require_all.rb +289 -0
- data/lib/arg_scanner/return_type_tracker.rb +25 -0
- data/lib/arg_scanner/starter.rb +20 -3
- data/lib/arg_scanner/state_tracker.rb +124 -0
- data/lib/arg_scanner/type_tracker.rb +66 -5
- data/lib/arg_scanner/version.rb +1 -1
- data/util/state_filter.rb +35 -0
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7016d5a2dddb4f8fd0be218fa11d5e1b65addeb
|
4
|
+
data.tar.gz: c7b233ada70e04c77b73f4b179cda89815f6f16b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c049b2d855fc27e37ff6feb1edad6713644d3955ce6780dc2b052ace6eea1c9afe2b010441106d437a568a52acb7ead358d0565648c85bb5def13ce67e82bf6a
|
7
|
+
data.tar.gz: bd3b07970807bff87c370be1fbc14acd9042a2e42090769056e6f7782ef0c02dc36f13375f5284350534bbfa6234f61871bb12643c4a9c9e71c57e3c0772de56
|
data/README.md
CHANGED
data/arg_scanner.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'arg_scanner/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "arg_scanner"
|
8
8
|
spec.version = ArgScanner::VERSION
|
9
|
-
spec.authors = ["Nickolay Viuginov", "Valentin Fondaratov"]
|
10
|
-
spec.email = ["viuginov.nickolay@gmail.com", "fondarat@gmail.com"]
|
9
|
+
spec.authors = ["Nickolay Viuginov", "Valentin Fondaratov", "Vladimir Koshelev"]
|
10
|
+
spec.email = ["viuginov.nickolay@gmail.com", "fondarat@gmail.com", "vkkoshelev@gmail.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{Program execution tracker to retrieve data types information}
|
13
13
|
spec.homepage = "https://github.com/jetbrains/ruby-type-inference"
|
@@ -33,5 +33,5 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency "bundler", "~> 1.13"
|
34
34
|
spec.add_development_dependency "rake", "~> 12.0"
|
35
35
|
spec.add_development_dependency "rake-compiler"
|
36
|
-
spec.add_development_dependency "debase-ruby_core_source", "
|
36
|
+
spec.add_development_dependency "debase-ruby_core_source", ">= 0.10.0"
|
37
37
|
end
|
data/bin/arg-scanner
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'arg_scanner/options'
|
5
|
+
require 'arg_scanner/version'
|
5
6
|
|
6
7
|
options = ArgScanner::OPTIONS
|
7
8
|
option_parser = OptionParser.new do |opts|
|
8
|
-
opts.banner = <<~EOB
|
9
|
+
opts.banner = "arg-scanner #{ArgScanner::VERSION}" + <<~EOB
|
10
|
+
|
9
11
|
Usage: arg-scanner [OPTIONS] <ruby cmdline>
|
10
12
|
arg-scanner is a ruby script mediator supposed to be run from the command line or IDE.
|
11
13
|
The data will be sent to a signature server so it must be running during arg-scanner execution.
|
@@ -22,6 +24,30 @@ option_parser = OptionParser.new do |opts|
|
|
22
24
|
opts.on("--no-local", "local source treatment: ignore, do not send data from local sources") do
|
23
25
|
options.no_local = true
|
24
26
|
end
|
27
|
+
opts.on("--type-tracker", "enable type tracker") do
|
28
|
+
options.enable_type_tracker = true
|
29
|
+
end
|
30
|
+
opts.on("--state-tracker", "enable state tracker") do
|
31
|
+
options.enable_state_tracker = true
|
32
|
+
end
|
33
|
+
opts.on("--return-type-tracker", "enable return type tracker") do
|
34
|
+
options.enable_return_type_tracker = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("--no-type-tracker", "disable type tracker") do
|
38
|
+
options.enable_type_tracker = false
|
39
|
+
end
|
40
|
+
opts.on("--no-state-tracker", "disable state tracker") do
|
41
|
+
options.enable_state_tracker = false
|
42
|
+
end
|
43
|
+
opts.on("--no-return-type-tracker", "disable return type tracker") do
|
44
|
+
options.enable_return_type_tracker = false
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("--output-dir=[Dir]", String, "specify output directory (ignored by type tracker)") do |dir|
|
48
|
+
options.output_dir = dir
|
49
|
+
end
|
50
|
+
|
25
51
|
end
|
26
52
|
|
27
53
|
begin
|
@@ -8,6 +8,12 @@
|
|
8
8
|
|
9
9
|
//#define DEBUG_ARG_SCANNER 1
|
10
10
|
|
11
|
+
#if RUBY_API_VERSION_CODE >= 20500
|
12
|
+
#define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec.cfp)
|
13
|
+
#else
|
14
|
+
#define TH_CFP(thread) ((rb_control_frame_t *)(thread)->cfp)
|
15
|
+
#endif
|
16
|
+
|
11
17
|
#ifdef DEBUG_ARG_SCANNER
|
12
18
|
#define LOG(f, args...) { fprintf(stderr, "DEBUG: '%s'=", #args); fprintf(stderr, f, ##args); fflush(stderr); }
|
13
19
|
#else
|
@@ -186,7 +192,7 @@ get_call_info()
|
|
186
192
|
call_info_t *info;
|
187
193
|
|
188
194
|
thread = ruby_current_thread;
|
189
|
-
cfp = thread
|
195
|
+
cfp = TH_CFP(thread);
|
190
196
|
info = malloc(sizeof(call_info_t));
|
191
197
|
//info = ALLOC(call_info_t);
|
192
198
|
//info = malloc;
|
@@ -297,11 +303,6 @@ calc_sane_class_name(VALUE ptr)
|
|
297
303
|
klass_name = "<err>";
|
298
304
|
}
|
299
305
|
|
300
|
-
if (strlen(klass_name) >= 200)
|
301
|
-
{
|
302
|
-
fprintf(stderr, "ERROR: too long class name: '%s'\n", klass_name);
|
303
|
-
assert(false);
|
304
|
-
}
|
305
306
|
return klass_name;
|
306
307
|
}
|
307
308
|
|
@@ -370,7 +371,7 @@ get_args_info()
|
|
370
371
|
rb_control_frame_t *cfp;
|
371
372
|
|
372
373
|
thread = ruby_current_thread;
|
373
|
-
cfp = thread
|
374
|
+
cfp = TH_CFP(thread);
|
374
375
|
|
375
376
|
cfp += 3;
|
376
377
|
|
@@ -406,7 +407,10 @@ get_args_info()
|
|
406
407
|
|
407
408
|
ans_iterator = 0;
|
408
409
|
|
409
|
-
|
410
|
+
int new_version_flag = strcmp(RUBY_VERSION, "2.4.0") >= 0 ? 1 : 0;
|
411
|
+
LOG("%d\n", new_version_flag);
|
412
|
+
|
413
|
+
for(i = param_size - 1 - new_version_flag, types_iterator = 0; (size_t)types_iterator < param_size; i--, types_iterator++)
|
410
414
|
{
|
411
415
|
types[types_iterator] = calc_sane_class_name(*(ep + i - 1));
|
412
416
|
types_ids[types_iterator] = i - 1;
|
@@ -437,10 +441,7 @@ get_args_info()
|
|
437
441
|
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
|
438
442
|
|
439
443
|
char* type;
|
440
|
-
|
441
|
-
type = "Array/empty";
|
442
|
-
else
|
443
|
-
type = types[types_iterator];
|
444
|
+
type = types[types_iterator];
|
444
445
|
|
445
446
|
ans[ans_iterator] = fast_join(',', 3, "REST", type, name);
|
446
447
|
}
|
@@ -472,7 +473,7 @@ get_args_info()
|
|
472
473
|
}
|
473
474
|
}
|
474
475
|
|
475
|
-
if(param_size - has_block > 1 && has_kwrest)
|
476
|
+
if(param_size - has_block > 1 && has_kwrest && TYPE(*(ep + types_ids[types_iterator])) == T_FIXNUM)
|
476
477
|
types_iterator--;
|
477
478
|
|
478
479
|
for(i = 0; i < has_kwrest; i++, ans_iterator++, types_iterator--)
|
@@ -482,10 +483,7 @@ get_args_info()
|
|
482
483
|
LOG("%d\n", rb_hash_size(*(ep + types_ids[types_iterator])));
|
483
484
|
char* type;
|
484
485
|
|
485
|
-
|
486
|
-
type = "Hash/empty";
|
487
|
-
else
|
488
|
-
type = types[types_iterator];
|
486
|
+
type = types[types_iterator];
|
489
487
|
|
490
488
|
ans[ans_iterator] = fast_join(',', 3, "KEYREST", type, name);
|
491
489
|
}
|
@@ -558,7 +556,7 @@ is_call_info_needed()
|
|
558
556
|
rb_control_frame_t *cfp;
|
559
557
|
|
560
558
|
thread = ruby_current_thread;
|
561
|
-
cfp = thread
|
559
|
+
cfp = TH_CFP(thread);
|
562
560
|
cfp += 3;
|
563
561
|
|
564
562
|
return (cfp->iseq->body->param.flags.has_opt
|
data/lib/arg_scanner.rb
CHANGED
data/lib/arg_scanner/options.rb
CHANGED
@@ -4,12 +4,20 @@ module ArgScanner
|
|
4
4
|
OPTIONS = OpenStruct.new(
|
5
5
|
:local_version => ENV['ARG_SCANNER_LOCAL_VERSION'] || '0',
|
6
6
|
:no_local => ENV['ARG_SCANNER_NO_LOCAL'] ? true : false,
|
7
|
-
:project_roots => ((ENV['ARG_SCANNER_PROJECT_ROOTS'] || "").split ':')
|
7
|
+
:project_roots => ((ENV['ARG_SCANNER_PROJECT_ROOTS'] || "").split ':'),
|
8
|
+
:enable_type_tracker => ENV['ARG_SCANNER_ENABLE_TYPE_TRACKER'],
|
9
|
+
:enable_state_tracker => ENV['ARG_SCANNER_ENABLE_STATE_TRACKER'],
|
10
|
+
:enable_return_type_tracker => ENV['ARG_SCANNER_ENABLE_RETURN_TYPE_TRACKER'],
|
11
|
+
:output_directory => ENV['ARG_SCANNER_DIR'],
|
8
12
|
)
|
9
13
|
|
10
14
|
def OPTIONS.set_env
|
11
15
|
ENV['ARG_SCANNER_LOCAL_VERSION'] = self.local_version.to_s
|
12
16
|
ENV['ARG_SCANNER_NO_LOCAL'] = self.no_local ? "1" : nil
|
13
17
|
ENV['ARG_SCANNER_PROJECT_ROOTS'] = self.project_roots.join ':'
|
18
|
+
ENV['ARG_SCANNER_ENABLE_TYPE_TRACKER'] = self.enable_type_tracker ? "1" : nil
|
19
|
+
ENV['ARG_SCANNER_ENABLE_STATE_TRACKER'] = self.enable_state_tracker ? "1" : nil
|
20
|
+
ENV['ARG_SCANNER_ENABLE_RETURN_TYPE_TRACKER'] = self.enable_return_type_tracker ? "1" : nil
|
21
|
+
ENV['ARG_SCANNER_DIR'] = self.output_directory
|
14
22
|
end
|
15
23
|
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
# Copyright (c) 2009 Jarmo Pertman
|
2
|
+
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
module RequireAll
|
23
|
+
# A wonderfully simple way to load your code.
|
24
|
+
#
|
25
|
+
# The easiest way to use require_all is to just point it at a directory
|
26
|
+
# containing a bunch of .rb files. These files can be nested under
|
27
|
+
# subdirectories as well:
|
28
|
+
#
|
29
|
+
# require_all 'lib'
|
30
|
+
#
|
31
|
+
# This will find all the .rb files under the lib directory and load them.
|
32
|
+
# The proper order to load them in will be determined automatically.
|
33
|
+
#
|
34
|
+
# If the dependencies between the matched files are unresolvable, it will
|
35
|
+
# throw the first unresolvable NameError.
|
36
|
+
#
|
37
|
+
# You can also give it a glob, which will enumerate all the matching files:
|
38
|
+
#
|
39
|
+
# require_all 'lib/**/*.rb'
|
40
|
+
#
|
41
|
+
# It will also accept an array of files:
|
42
|
+
#
|
43
|
+
# require_all Dir.glob("blah/**/*.rb").reject { |f| stupid_file(f) }
|
44
|
+
#
|
45
|
+
# Or if you want, just list the files directly as arguments:
|
46
|
+
#
|
47
|
+
# require_all 'lib/a.rb', 'lib/b.rb', 'lib/c.rb', 'lib/d.rb'
|
48
|
+
#
|
49
|
+
def require_all(*args)
|
50
|
+
# Handle passing an array as an argument
|
51
|
+
args.flatten!
|
52
|
+
|
53
|
+
options = {:method => :require}
|
54
|
+
options.merge!(args.pop) if args.last.is_a?(Hash)
|
55
|
+
|
56
|
+
if args.empty?
|
57
|
+
puts "no files were loaded due to an empty Array" if $DEBUG
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
if args.size > 1
|
62
|
+
# Expand files below directories
|
63
|
+
files = args.map do |path|
|
64
|
+
if File.directory? path
|
65
|
+
Dir[File.join(path, '**', '*.rb')]
|
66
|
+
else
|
67
|
+
path
|
68
|
+
end
|
69
|
+
end.flatten
|
70
|
+
else
|
71
|
+
arg = args.first
|
72
|
+
begin
|
73
|
+
# Try assuming we're doing plain ol' require compat
|
74
|
+
stat = File.stat(arg)
|
75
|
+
|
76
|
+
if stat.file?
|
77
|
+
files = [arg]
|
78
|
+
elsif stat.directory?
|
79
|
+
files = Dir.glob File.join(arg, '**', '*.rb')
|
80
|
+
else
|
81
|
+
raise ArgumentError, "#{arg} isn't a file or directory"
|
82
|
+
end
|
83
|
+
rescue SystemCallError
|
84
|
+
# If the stat failed, maybe we have a glob!
|
85
|
+
files = Dir.glob arg
|
86
|
+
|
87
|
+
# Maybe it's an .rb file and the .rb was omitted
|
88
|
+
if File.file?(arg + '.rb')
|
89
|
+
file = arg + '.rb'
|
90
|
+
options[:method] != :autoload ? Kernel.send(options[:method], file) : __autoload(file, file, options)
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
|
94
|
+
# If we ain't got no files, the glob failed
|
95
|
+
raise LoadError, "no such file to load -- #{arg}" if files.empty?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
return if files.empty?
|
100
|
+
|
101
|
+
if options[:method] == :autoload
|
102
|
+
files.map! { |file_| [file_, File.expand_path(file_)] }
|
103
|
+
files.each do |file_, full_path|
|
104
|
+
__autoload(file_, full_path, options)
|
105
|
+
end
|
106
|
+
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
|
110
|
+
files.map! { |file_| File.expand_path file_ }
|
111
|
+
files.sort!
|
112
|
+
|
113
|
+
begin
|
114
|
+
failed = []
|
115
|
+
first_name_error = nil
|
116
|
+
|
117
|
+
# Attempt to load each file, rescuing which ones raise NameError for
|
118
|
+
# undefined constants. Keep trying to successively reload files that
|
119
|
+
# previously caused NameErrors until they've all been loaded or no new
|
120
|
+
# files can be loaded, indicating unresolvable dependencies.
|
121
|
+
files.each do |file_|
|
122
|
+
begin
|
123
|
+
Kernel.send(options[:method], file_)
|
124
|
+
rescue NameError => ex
|
125
|
+
failed << file_
|
126
|
+
first_name_error ||= ex
|
127
|
+
rescue ArgumentError => ex
|
128
|
+
# Work around ActiveSuport freaking out... *sigh*
|
129
|
+
#
|
130
|
+
# ActiveSupport sometimes throws these exceptions and I really
|
131
|
+
# have no idea why. Code loading will work successfully if these
|
132
|
+
# exceptions are swallowed, although I've run into strange
|
133
|
+
# nondeterministic behaviors with constants mysteriously vanishing.
|
134
|
+
# I've gone spelunking through dependencies.rb looking for what
|
135
|
+
# exactly is going on, but all I ended up doing was making my eyes
|
136
|
+
# bleed.
|
137
|
+
#
|
138
|
+
# FIXME: If you can understand ActiveSupport's dependencies.rb
|
139
|
+
# better than I do I would *love* to find a better solution
|
140
|
+
raise unless ex.message["is not missing constant"]
|
141
|
+
|
142
|
+
STDERR.puts "Warning: require_all swallowed ActiveSupport 'is not missing constant' error"
|
143
|
+
STDERR.puts ex.backtrace[0..9]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# If this pass didn't resolve any NameErrors, we've hit an unresolvable
|
148
|
+
# dependency, so raise one of the exceptions we encountered.
|
149
|
+
if failed.size == files.size
|
150
|
+
raise first_name_error
|
151
|
+
else
|
152
|
+
files = failed
|
153
|
+
end
|
154
|
+
end until failed.empty?
|
155
|
+
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
# Works like require_all, but paths are relative to the caller rather than
|
160
|
+
# the current working directory
|
161
|
+
def require_rel(*paths)
|
162
|
+
# Handle passing an array as an argument
|
163
|
+
paths.flatten!
|
164
|
+
return false if paths.empty?
|
165
|
+
|
166
|
+
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
|
167
|
+
paths.each do |path|
|
168
|
+
require_all File.join(source_directory, path)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Loads all files like require_all instead of requiring
|
173
|
+
def load_all(*paths)
|
174
|
+
require_all paths, :method => :load
|
175
|
+
end
|
176
|
+
|
177
|
+
# Loads all files by using relative paths of the caller rather than
|
178
|
+
# the current working directory
|
179
|
+
def load_rel(*paths)
|
180
|
+
paths.flatten!
|
181
|
+
return false if paths.empty?
|
182
|
+
|
183
|
+
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
|
184
|
+
paths.each do |path|
|
185
|
+
require_all File.join(source_directory, path), :method => :load
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Performs Kernel#autoload on all of the files rather than requiring immediately.
|
190
|
+
#
|
191
|
+
# Note that all Ruby files inside of the specified directories should have same module name as
|
192
|
+
# the directory itself and file names should reflect the class/module names.
|
193
|
+
# For example if there is a my_file.rb in directories dir1/dir2/ then
|
194
|
+
# there should be a declaration like this in my_file.rb:
|
195
|
+
# module Dir1
|
196
|
+
# module Dir2
|
197
|
+
# class MyFile
|
198
|
+
# ...
|
199
|
+
# end
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# If the filename and namespaces won't match then my_file.rb will be loaded into wrong module!
|
204
|
+
# Better to fix these files.
|
205
|
+
#
|
206
|
+
# Set $DEBUG=true to see how files will be autoloaded if experiencing any problems.
|
207
|
+
#
|
208
|
+
# If trying to perform autoload on some individual file or some inner module, then you'd have
|
209
|
+
# to always specify *:base_dir* option to specify where top-level namespace resides.
|
210
|
+
# Otherwise it's impossible to know the namespace of the loaded files.
|
211
|
+
#
|
212
|
+
# For example loading only my_file.rb from dir1/dir2 with autoload_all:
|
213
|
+
#
|
214
|
+
# autoload_all File.dirname(__FILE__) + '/dir1/dir2/my_file',
|
215
|
+
# :base_dir => File.dirname(__FILE__) + '/dir1'
|
216
|
+
#
|
217
|
+
# WARNING: All modules will be created even if files themselves aren't loaded yet, meaning
|
218
|
+
# that all the code which depends of the modules being loaded or not will not work, like usages
|
219
|
+
# of define? and it's friends.
|
220
|
+
#
|
221
|
+
# Also, normal caveats of using Kernel#autoload apply - you have to remember that before
|
222
|
+
# applying any monkey-patches to code using autoload, you'll have to reference the full constant
|
223
|
+
# to load the code before applying your patch!
|
224
|
+
|
225
|
+
def autoload_all(*paths)
|
226
|
+
paths.flatten!
|
227
|
+
return false if paths.empty?
|
228
|
+
require "pathname"
|
229
|
+
|
230
|
+
options = {:method => :autoload}
|
231
|
+
options.merge!(paths.pop) if paths.last.is_a?(Hash)
|
232
|
+
|
233
|
+
paths.each do |path|
|
234
|
+
require_all path, {:base_dir => path}.merge(options)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Performs autoloading relatively from the caller instead of using current working directory
|
239
|
+
def autoload_rel(*paths)
|
240
|
+
paths.flatten!
|
241
|
+
return false if paths.empty?
|
242
|
+
require "pathname"
|
243
|
+
|
244
|
+
options = {:method => :autoload}
|
245
|
+
options.merge!(paths.pop) if paths.last.is_a?(Hash)
|
246
|
+
|
247
|
+
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
|
248
|
+
paths.each do |path|
|
249
|
+
file_path = Pathname.new(source_directory).join(path).to_s
|
250
|
+
require_all file_path, {:method => :autoload,
|
251
|
+
:base_dir => source_directory}.merge(options)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
private
|
256
|
+
|
257
|
+
def __autoload(file, full_path, options)
|
258
|
+
last_module = "Object" # default constant where namespaces are created into
|
259
|
+
begin
|
260
|
+
base_dir = Pathname.new(options[:base_dir]).realpath
|
261
|
+
rescue Errno::ENOENT
|
262
|
+
raise LoadError, ":base_dir doesn't exist at #{options[:base_dir]}"
|
263
|
+
end
|
264
|
+
Pathname.new(file).realpath.descend do |entry|
|
265
|
+
# skip until *entry* is same as desired directory
|
266
|
+
# or anything inside of it avoiding to create modules
|
267
|
+
# from the top-level directories
|
268
|
+
next if (entry <=> base_dir) < 0
|
269
|
+
|
270
|
+
# get the module into which a new module is created or
|
271
|
+
# autoload performed
|
272
|
+
mod = Object.class_eval(last_module)
|
273
|
+
|
274
|
+
without_ext = entry.basename(entry.extname).to_s
|
275
|
+
const = without_ext.split("_").map {|word| word.capitalize}.join
|
276
|
+
|
277
|
+
if entry.directory?
|
278
|
+
mod.class_eval "module #{const} end"
|
279
|
+
last_module += "::#{const}"
|
280
|
+
else
|
281
|
+
mod.class_eval do
|
282
|
+
puts "autoloading #{mod}::#{const} from #{full_path}" if $DEBUG
|
283
|
+
autoload const, full_path
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ArgScanner
|
4
|
+
class ReturnTypeTracker
|
5
|
+
def initialize
|
6
|
+
@result = Set.new
|
7
|
+
TracePoint.new(:return) do |tp|
|
8
|
+
@result.add( {
|
9
|
+
def: tp.defined_class,
|
10
|
+
name: tp.method_id,
|
11
|
+
ret: tp.return_value.class
|
12
|
+
})
|
13
|
+
end.enable
|
14
|
+
at_exit do
|
15
|
+
dir = ENV["ARG_SCANNER_DIR"]
|
16
|
+
dir = "." if dir.nil? || dir == ""
|
17
|
+
path = dir + "/calls-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}-#{Process.pid}.json"
|
18
|
+
require 'json'
|
19
|
+
File.open(path,"w") { |file| file.puts(JSON.dump(@result.to_a)) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
data/lib/arg_scanner/starter.rb
CHANGED
@@ -1,4 +1,21 @@
|
|
1
|
-
|
1
|
+
unless ENV["ARG_SCANNER_ENABLE_STATE_TRACKER"].nil?
|
2
|
+
require_relative 'state_tracker'
|
3
|
+
ArgScanner::StateTracker.new
|
4
|
+
end
|
5
|
+
|
6
|
+
unless ENV["ARG_SCANNER_ENABLE_RETURN_TYPE_TRACKER"].nil?
|
7
|
+
require_relative 'return_type_tracker'
|
8
|
+
ArgScanner::ReturnTypeTracker.new
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
unless ENV["ARG_SCANNER_ENABLE_TYPE_TRACKER"].nil?
|
13
|
+
require_relative 'arg_scanner'
|
14
|
+
require_relative 'type_tracker'
|
15
|
+
|
16
|
+
# instantiating type tracker will enable calls tracing and sending the data
|
17
|
+
ArgScanner::TypeTracker.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
|
2
21
|
|
3
|
-
# instantiating type tracker will enable calls tracing and sending the data
|
4
|
-
ArgScanner::TypeTracker.instance
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require "set"
|
2
|
+
require_relative "require_all"
|
3
|
+
|
4
|
+
|
5
|
+
module ArgScanner
|
6
|
+
class StateTracker
|
7
|
+
def initialize
|
8
|
+
at_exit do
|
9
|
+
dir = ENV["ARG_SCANNER_DIR"]
|
10
|
+
dir = "." if dir.nil? || dir == ""
|
11
|
+
path = dir + "/" + "classes-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}-#{Process.pid}.json"
|
12
|
+
begin
|
13
|
+
RequireAll.require_all Rails.root.join('lib')
|
14
|
+
rescue => e
|
15
|
+
end
|
16
|
+
begin
|
17
|
+
Rails.application.eager_load!
|
18
|
+
rescue => e
|
19
|
+
end
|
20
|
+
|
21
|
+
File.open(path,"w") { |file| print_json(file) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def print_json(file)
|
27
|
+
result = {
|
28
|
+
:top_level_constants => parse_top_level_constants,
|
29
|
+
:modules => modules_to_json,
|
30
|
+
:load_path => $:
|
31
|
+
}
|
32
|
+
require "json"
|
33
|
+
file.puts(JSON.dump(result))
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_top_level_constants
|
37
|
+
Module.constants.select { |const| Module.const_defined?(const)}.map do |const|
|
38
|
+
begin
|
39
|
+
value = Module.const_get(const)
|
40
|
+
(!value.is_a? Module) ? {
|
41
|
+
:name => const,
|
42
|
+
:class_name => value.class,
|
43
|
+
:extended => get_extra_methods(value)} : nil
|
44
|
+
rescue Exception => e
|
45
|
+
end
|
46
|
+
end.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_extra_methods(value)
|
50
|
+
begin
|
51
|
+
(value.methods - value.class.public_instance_methods).map do |method_name|
|
52
|
+
method = value.public_method(method_name)
|
53
|
+
method.owner
|
54
|
+
end.uniq
|
55
|
+
rescue Exception => e
|
56
|
+
value.methods - value.class.instance_methods
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_constants_of_class(constants, parent, klass)
|
62
|
+
constants.select {|const| parent.const_defined?(const)}.map do |const|
|
63
|
+
begin
|
64
|
+
parent.const_get(const)
|
65
|
+
rescue Exception => e
|
66
|
+
end
|
67
|
+
end.select { |const| const.is_a? klass}
|
68
|
+
rescue => e
|
69
|
+
$stderr.puts(e)
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def get_modules(mod)
|
75
|
+
get_constants_of_class(mod.constants, mod, Module)
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_all_modules
|
79
|
+
queue = Queue.new
|
80
|
+
visited = Set.new
|
81
|
+
get_modules(Module).each {|mod| queue.push(mod); visited.add(mod)}
|
82
|
+
|
83
|
+
until queue.empty? do
|
84
|
+
mod = queue.pop
|
85
|
+
get_modules(mod).each do |child|
|
86
|
+
unless visited.include?(child)
|
87
|
+
queue.push(child)
|
88
|
+
visited.add(child)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
visited
|
93
|
+
end
|
94
|
+
|
95
|
+
def method_to_json(method)
|
96
|
+
ret = {
|
97
|
+
:name => method.name,
|
98
|
+
:parameters => method.parameters
|
99
|
+
}
|
100
|
+
unless method.source_location.nil?
|
101
|
+
ret[:path] = method.source_location[0]
|
102
|
+
ret[:line] = method.source_location[1]
|
103
|
+
end
|
104
|
+
ret
|
105
|
+
end
|
106
|
+
|
107
|
+
def module_to_json(mod)
|
108
|
+
ret = {
|
109
|
+
:name => mod.to_s,
|
110
|
+
:type => mod.class.to_s,
|
111
|
+
:singleton_class_included => mod.singleton_class.included_modules,
|
112
|
+
:included => mod.included_modules,
|
113
|
+
:class_methods => mod.methods(false).map {|method| method_to_json(mod.method(method))},
|
114
|
+
:instance_methods => mod.instance_methods(false).map {|method| method_to_json(mod.instance_method(method))}
|
115
|
+
}
|
116
|
+
ret[:superclass] = mod.superclass if mod.is_a? Class
|
117
|
+
ret
|
118
|
+
end
|
119
|
+
|
120
|
+
def modules_to_json
|
121
|
+
get_all_modules.map {|mod| module_to_json(mod)}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,10 +1,53 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'socket'
|
3
3
|
require 'singleton'
|
4
|
+
require 'thread'
|
4
5
|
|
5
6
|
require_relative 'options'
|
6
7
|
|
7
8
|
module ArgScanner
|
9
|
+
|
10
|
+
class TypeTrackerPerformanceMonitor
|
11
|
+
def initialize
|
12
|
+
@enable_debug = ENV["ARG_SCANNER_DEBUG"]
|
13
|
+
@call_counter = 0
|
14
|
+
@handled_call_counter = 0
|
15
|
+
@submitted_call_counter = 0
|
16
|
+
@old_handled_call_counter = 0
|
17
|
+
@time = Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def on_call
|
22
|
+
@submitted_call_counter += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_return
|
26
|
+
@call_counter += 1
|
27
|
+
|
28
|
+
if enable_debug && call_counter % 100000 == 0
|
29
|
+
$stderr.puts("calls #{call_counter} handled #{handled_call_counter} submitted #{submitted_call_counter}"\
|
30
|
+
"delta #{handled_call_counter - old_handled_call_counter} time #{Time.now - @time}")
|
31
|
+
@old_handled_call_counter = handled_call_counter
|
32
|
+
@time = Time.now
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_handled_return
|
37
|
+
@handled_call_counter += 1
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_accessor :submitted_call_counter
|
43
|
+
attr_accessor :handled_call_counter
|
44
|
+
attr_accessor :old_handled_call_counter
|
45
|
+
attr_accessor :call_counter
|
46
|
+
attr_accessor :enable_debug
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
8
51
|
class TypeTracker
|
9
52
|
include Singleton
|
10
53
|
|
@@ -13,7 +56,10 @@ module ArgScanner
|
|
13
56
|
def initialize
|
14
57
|
@cache = Set.new
|
15
58
|
@socket = TCPSocket.new('127.0.0.1', 7777)
|
16
|
-
|
59
|
+
@mutex = Mutex.new
|
60
|
+
@prefix = ENV["ARG_SCANNER_PREFIX"]
|
61
|
+
@enable_debug = ENV["ARG_SCANNER_DEBUG"]
|
62
|
+
@performance_monitor = if @enable_debug then TypeTrackerPerformanceMonitor.new else nil end
|
17
63
|
TracePoint.trace(:call, :return) do |tp|
|
18
64
|
case tp.event
|
19
65
|
when :call
|
@@ -24,8 +70,13 @@ module ArgScanner
|
|
24
70
|
end
|
25
71
|
end
|
26
72
|
|
73
|
+
attr_accessor :enable_debug
|
74
|
+
attr_accessor :performance_monitor
|
27
75
|
attr_accessor :cache
|
28
76
|
attr_accessor :socket
|
77
|
+
attr_accessor :mutex
|
78
|
+
attr_accessor :prefix
|
79
|
+
|
29
80
|
|
30
81
|
|
31
82
|
# @param [String] path
|
@@ -50,20 +101,30 @@ module ArgScanner
|
|
50
101
|
end
|
51
102
|
|
52
103
|
def put_to_socket(message)
|
53
|
-
socket.puts(message)
|
104
|
+
mutex.synchronize { socket.puts(message) }
|
54
105
|
end
|
55
106
|
|
56
107
|
private
|
57
108
|
def handle_call(tp)
|
58
109
|
#handle_call(VALUE self, VALUE lineno, VALUE method_name, VALUE path)
|
59
|
-
|
60
|
-
|
110
|
+
if prefix.nil? || tp.path.start_with?(prefix)
|
111
|
+
performance_monitor.on_call unless performance_monitor.nil?
|
112
|
+
signature = ArgScanner.handle_call(tp.lineno, tp.method_id, tp.path)
|
113
|
+
signatures.push(signature)
|
114
|
+
else
|
115
|
+
signatures.push(false)
|
116
|
+
end
|
61
117
|
end
|
62
118
|
|
63
119
|
def handle_return(tp)
|
64
120
|
sigi = signatures
|
121
|
+
performance_monitor.on_return unless performance_monitor.nil?
|
122
|
+
|
65
123
|
unless sigi.empty?
|
66
124
|
signature = sigi.pop
|
125
|
+
return unless signature
|
126
|
+
|
127
|
+
performance_monitor.on_handled_return unless performance_monitor.nil?
|
67
128
|
|
68
129
|
defined_class = tp.defined_class
|
69
130
|
return if !defined_class || defined_class.singleton_class?
|
@@ -74,7 +135,7 @@ module ArgScanner
|
|
74
135
|
return if !receiver_name || !receiver_name.to_s || receiver_name.to_s.length > 200
|
75
136
|
|
76
137
|
json = ArgScanner.handle_return(signature, return_type_name) +
|
77
|
-
|
138
|
+
"\"receiver_name\":\"#{receiver_name}\",\"return_type_name\":\"#{return_type_name}\","
|
78
139
|
|
79
140
|
if cache.add?(json)
|
80
141
|
gem_name, gem_version = TypeTracker.extract_gem_name_and_version(tp.path)
|
data/lib/arg_scanner/version.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
if ARGV.length < 3
|
7
|
+
puts("state_filter.rb <in-file> <out-file> [<list-of-names]")
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
json = JSON.parse(File.read(ARGV[0]))
|
12
|
+
|
13
|
+
modules2names = {}
|
14
|
+
|
15
|
+
json["modules"].each {|mod| modules2names[mod["name"]] = mod}
|
16
|
+
|
17
|
+
visited = Set.new
|
18
|
+
queue = Queue.new
|
19
|
+
ARGV[2..-1].each do |it|
|
20
|
+
visited.add(it)
|
21
|
+
queue.push(it)
|
22
|
+
end
|
23
|
+
|
24
|
+
until queue.empty? do
|
25
|
+
elem = modules2names[queue.pop] || next
|
26
|
+
(elem["singleton_class_included"] + elem["included"] + [elem["superclass"]]).each do |mod|
|
27
|
+
queue.push(mod) if visited.add?(mod)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
output_modules = visited.map do |mod|
|
32
|
+
modules2names[mod]
|
33
|
+
end.compact
|
34
|
+
|
35
|
+
File.write(ARGV[1], JSON.pretty_generate({:modules => output_modules, :load_path => json["load_path"]}))
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arg_scanner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nickolay Viuginov
|
8
8
|
- Valentin Fondaratov
|
9
|
+
- Vladimir Koshelev
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2017-
|
13
|
+
date: 2017-11-23 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -57,20 +58,21 @@ dependencies:
|
|
57
58
|
name: debase-ruby_core_source
|
58
59
|
requirement: !ruby/object:Gem::Requirement
|
59
60
|
requirements:
|
60
|
-
- - "
|
61
|
+
- - ">="
|
61
62
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
63
|
+
version: 0.10.0
|
63
64
|
type: :development
|
64
65
|
prerelease: false
|
65
66
|
version_requirements: !ruby/object:Gem::Requirement
|
66
67
|
requirements:
|
67
|
-
- - "
|
68
|
+
- - ">="
|
68
69
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
70
|
+
version: 0.10.0
|
70
71
|
description:
|
71
72
|
email:
|
72
73
|
- viuginov.nickolay@gmail.com
|
73
74
|
- fondarat@gmail.com
|
75
|
+
- vkkoshelev@gmail.com
|
74
76
|
executables:
|
75
77
|
- arg-scanner
|
76
78
|
- console
|
@@ -94,9 +96,13 @@ files:
|
|
94
96
|
- ext/arg_scanner/extconf.rb
|
95
97
|
- lib/arg_scanner.rb
|
96
98
|
- lib/arg_scanner/options.rb
|
99
|
+
- lib/arg_scanner/require_all.rb
|
100
|
+
- lib/arg_scanner/return_type_tracker.rb
|
97
101
|
- lib/arg_scanner/starter.rb
|
102
|
+
- lib/arg_scanner/state_tracker.rb
|
98
103
|
- lib/arg_scanner/type_tracker.rb
|
99
104
|
- lib/arg_scanner/version.rb
|
105
|
+
- util/state_filter.rb
|
100
106
|
homepage: https://github.com/jetbrains/ruby-type-inference
|
101
107
|
licenses:
|
102
108
|
- MIT
|
@@ -117,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
123
|
version: '0'
|
118
124
|
requirements: []
|
119
125
|
rubyforge_project:
|
120
|
-
rubygems_version: 2.6.
|
126
|
+
rubygems_version: 2.6.11
|
121
127
|
signing_key:
|
122
128
|
specification_version: 4
|
123
129
|
summary: Program execution tracker to retrieve data types information
|