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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a5018e4c54e0aac945b4eb3d1d19c0915109516
4
- data.tar.gz: 97a6c8b858413da9a447a4d34d7310ad9ab97f3b
3
+ metadata.gz: d7016d5a2dddb4f8fd0be218fa11d5e1b65addeb
4
+ data.tar.gz: c7b233ada70e04c77b73f4b179cda89815f6f16b
5
5
  SHA512:
6
- metadata.gz: d2b050da192b7def101aea6f66c8ac2fa6cb5e507fe01579bc96bcb129a64e04b8c272fcf1057b197aa53016b1527b2a10f32b5525573488e7ef92bf7d14b2f3
7
- data.tar.gz: 55d111320901c78a7a64a71c39c2ba2a991543db1993a5b6b795fd8adf2900202fd9297465829c82ec2b0ae559c9b21f9abca935a9d0992dd8cf580eca250882
6
+ metadata.gz: c049b2d855fc27e37ff6feb1edad6713644d3955ce6780dc2b052ace6eea1c9afe2b010441106d437a568a52acb7ead358d0565648c85bb5def13ce67e82bf6a
7
+ data.tar.gz: bd3b07970807bff87c370be1fbc14acd9042a2e42090769056e6f7782ef0c02dc36f13375f5284350534bbfa6234f61871bb12643c4a9c9e71c57e3c0772de56
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ArgScanner
1
+ # ArgScanner [![Gem Version](https://badge.fury.io/rb/arg_scanner.svg)](https://badge.fury.io/rb/arg_scanner)
2
2
 
3
3
  `arg_scanner` is a gem with the purpose to track all method calls and
4
4
  deliver the following information:
@@ -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", "~> 0.9.6"
36
+ spec.add_development_dependency "debase-ruby_core_source", ">= 0.10.0"
37
37
  end
@@ -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->cfp;
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->cfp;
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
- for(i = param_size - 1 - (RUBY_VERSION >= "2.4.1"), types_iterator = 0; (size_t)types_iterator < param_size; i--, types_iterator++)
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
- if(RARRAY_LEN(*(ep + types_ids[types_iterator])) == 0)
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
- if(rb_hash_size(*(ep + types_ids[types_iterator])) == 1)
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->cfp;
559
+ cfp = TH_CFP(thread);
562
560
  cfp += 3;
563
561
 
564
562
  return (cfp->iseq->body->param.flags.has_opt
@@ -1,6 +1,8 @@
1
1
  require "arg_scanner/version"
2
2
  require "arg_scanner/arg_scanner"
3
3
  require "arg_scanner/type_tracker"
4
+ require "arg_scanner/state_tracker"
5
+ require "arg_scanner/return_type_tracker"
4
6
 
5
7
  module ArgScanner
6
8
  # Your code goes here...
@@ -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
+
@@ -1,4 +1,21 @@
1
- require 'arg_scanner'
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
- signature = ArgScanner.handle_call(tp.lineno, tp.method_id, tp.path)
60
- signatures.push(signature)
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
- "\"receiver_name\":\"#{receiver_name}\",\"return_type_name\":\"#{return_type_name}\","
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)
@@ -1,3 +1,3 @@
1
1
  module ArgScanner
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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.1.9
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-09-13 00:00:00.000000000 Z
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.9.6
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.9.6
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.12
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