arg_scanner 0.1.9 → 0.2.0

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 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