system_navigation 0.1.0 → 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/CHANGELOG.md +4 -0
- data/README.md +67 -18
- data/VERSION +1 -1
- data/lib/system_navigation.rb +92 -23
- data/lib/system_navigation/compiled_method.rb +28 -4
- data/lib/system_navigation/expression_tree.rb +2 -0
- data/lib/system_navigation/instruction_stream/decoder.rb +24 -0
- data/lib/system_navigation/instruction_stream/instruction.rb +70 -12
- data/lib/system_navigation/method_query.rb +5 -5
- data/lib/system_navigation/module_refinement.rb +4 -4
- data/lib/system_navigation/ruby_environment.rb +29 -3
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9f601aa746ea3e24e18be9512a868d9da486641
|
4
|
+
data.tar.gz: d73ce895b246cf274edefb11849d0d28a92637a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b54b78bbe3cb2bdb37ca96e13b0cd2a45bf4acff23cf0a453a450f46d9b4622f600775fed775d93eedaf5d7452c1544040d9fb2883119c26eb4e60c74e53004
|
7
|
+
data.tar.gz: 43f8a64c12f39bf151260de576446b2ac445b081160eb6fd806cd79d0960582688324f55a6924ce39bbd93a1f642d0334b8903905de38bdd4ea1feeb1f43016e
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,39 @@
|
|
1
1
|
SystemNavigation
|
2
2
|
==
|
3
3
|
|
4
|
-
* Repository
|
4
|
+
* [Repository](https://github.com/kyrylo/system_navigation/)
|
5
|
+
* [Documentation](http://www.rubydoc.info/gems/system_navigation)
|
5
6
|
|
6
7
|
Description
|
7
8
|
-----------
|
8
9
|
|
9
|
-
SystemNavigation is a Ruby library that provides
|
10
|
-
|
11
|
-
|
10
|
+
SystemNavigation is a Ruby library that provides additional introspection
|
11
|
+
capabilities for Ruby programs. The library defines a number of useful methods
|
12
|
+
that allow querying:
|
12
13
|
|
13
|
-
|
14
|
+
* methods for instance, class or global variables
|
15
|
+
* methods for literals such as numbers, strings, symbols, etc.
|
16
|
+
* methods for finding a specific string (method source search)
|
17
|
+
* classes and modules that implement given methods
|
18
|
+
* classes and modules that send messages
|
19
|
+
* all classes and modules in gems
|
20
|
+
* and many more...
|
21
|
+
|
22
|
+
For the complete list of features please read the documentation or _read the
|
23
|
+
tests_. All interaction with the library is done via the `SystemNavigation`
|
24
|
+
class and its class methods. The description of the methods can be found in
|
25
|
+
these places:
|
26
|
+
|
27
|
+
* [SystemNavigation](http://www.rubydoc.info/gems/system_navigation/SystemNavigation)
|
28
|
+
* [SystemNavigation::RubyEnvironment](http://www.rubydoc.info/gems/system_navigation/SystemNavigation/RubyEnvironment)
|
29
|
+
|
30
|
+
Examples (full list in the documentation)
|
31
|
+
--
|
32
|
+
|
33
|
+
##### #all_accesses
|
34
|
+
|
35
|
+
Retrieve all methods that access instance variables in the given class/module
|
36
|
+
including its ancestors and children.
|
14
37
|
|
15
38
|
```ruby
|
16
39
|
module M
|
@@ -35,27 +58,48 @@ sn.all_accesses(to: :@num, from: A)
|
|
35
58
|
#=> [#<UnboundMethod: A#num>, #<UnboundMethod: A(M)#increment>, #<UnboundMethod: A#initialize>]
|
36
59
|
```
|
37
60
|
|
38
|
-
|
61
|
+
##### #all_implementors_of
|
39
62
|
|
40
|
-
|
41
|
-
------------
|
63
|
+
Find all classes and modules that implement the given message.
|
42
64
|
|
43
|
-
|
65
|
+
```ruby
|
66
|
+
sn = SystemNavigation.default
|
44
67
|
|
45
|
-
|
68
|
+
sn.all_implementors_of(:puts)
|
69
|
+
#=> [ARGF.class, IO, Kernel, ..., YARD::Logger]
|
70
|
+
```
|
46
71
|
|
47
|
-
|
48
|
-
---
|
72
|
+
##### #all_calls
|
49
73
|
|
50
|
-
|
74
|
+
Find all methods in Bundler that invoke the `1` literal.
|
51
75
|
|
52
|
-
|
53
|
-
|
54
|
-
|
76
|
+
```ruby
|
77
|
+
require 'bundler'
|
78
|
+
|
79
|
+
sn = SystemNavigation.default
|
80
|
+
|
81
|
+
sn.all_calls(on: 1, gem: 'bundler')
|
82
|
+
#=> [#<UnboundMethod: #<Class:Bundler>#with_clean_env>, #<UnboundMethod: #<Class:Bundler>#eval_gemspec>]
|
83
|
+
```
|
55
84
|
|
56
|
-
|
85
|
+
##### #all_objects
|
57
86
|
|
58
|
-
|
87
|
+
Retrieve all objects defined in the system.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
sn = SystemNavigation.default
|
91
|
+
|
92
|
+
sn.all_objects.map(&:class).uniq.count #=> 158
|
93
|
+
```
|
94
|
+
|
95
|
+
And many more...
|
96
|
+
|
97
|
+
Installation
|
98
|
+
------------
|
99
|
+
|
100
|
+
All you need is to install the gem.
|
101
|
+
|
102
|
+
gem install system_navigation
|
59
103
|
|
60
104
|
Limitations
|
61
105
|
-----------
|
@@ -64,6 +108,11 @@ Supports *only* CRuby.
|
|
64
108
|
|
65
109
|
* CRuby 2.2.2 and higher
|
66
110
|
|
111
|
+
Credits
|
112
|
+
-------
|
113
|
+
|
114
|
+
* Inspired by Smalltalk
|
115
|
+
|
67
116
|
License
|
68
117
|
-------
|
69
118
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/system_navigation.rb
CHANGED
@@ -17,6 +17,12 @@ require_relative 'system_navigation/instruction_stream/decoder'
|
|
17
17
|
require_relative 'system_navigation/instruction_stream/instruction'
|
18
18
|
require_relative 'system_navigation/instruction_stream/instruction/attr_instruction'
|
19
19
|
|
20
|
+
# SystemNavigation is a class that provides some introspection capabilities. It
|
21
|
+
# is based on a Smalltalk class with a similar name. This is the only public
|
22
|
+
# class in this library.
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
# @since 0.1.0
|
20
26
|
class SystemNavigation
|
21
27
|
# The VERSION file must be in the root directory of the library.
|
22
28
|
VERSION_FILE = File.expand_path('../../VERSION', __FILE__)
|
@@ -31,19 +37,35 @@ class SystemNavigation
|
|
31
37
|
:all_behaviors, :all_classes, :all_classes_and_modules,
|
32
38
|
:all_modules, :all_objects
|
33
39
|
|
40
|
+
##
|
41
|
+
# Creates a new instance of SystemNavigation. It is added for compatibility
|
42
|
+
# with Smalltalk users.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# require 'system_navigation'
|
46
|
+
#
|
47
|
+
# sn = SystemNavigation.default
|
34
48
|
def self.default
|
35
49
|
self.new
|
36
50
|
end
|
37
51
|
|
52
|
+
VAR_TEMPLATE = /\A[\$@]@?.+/
|
53
|
+
private_constant :VAR_TEMPLATE
|
54
|
+
|
38
55
|
def initialize
|
39
56
|
@environment = SystemNavigation::RubyEnvironment.new
|
40
57
|
end
|
41
58
|
|
42
59
|
##
|
43
|
-
# Query methods for instance variables in descending (subclasses)
|
44
|
-
# ascending (superclasses) fashion.
|
60
|
+
# Query methods for instance/global/class variables in descending (subclasses)
|
61
|
+
# and ascending (superclasses) fashion.
|
45
62
|
#
|
46
|
-
# @
|
63
|
+
# @note This is a very costly operation, if you don't provide the +from+
|
64
|
+
# argument
|
65
|
+
# @note This method _does_ _not_ perform _global_ queries,
|
66
|
+
# only relative to +from+
|
67
|
+
#
|
68
|
+
# @example Global scope (start search from BasicObject)
|
47
69
|
# class A
|
48
70
|
# def initialize
|
49
71
|
# @foo = 1
|
@@ -57,7 +79,7 @@ class SystemNavigation
|
|
57
79
|
# sn.all_accesses(to: :@foo)
|
58
80
|
# #=> [#<UnboundMethod: A#initialize>, #<UnboundMethod: B#foo>]
|
59
81
|
#
|
60
|
-
# @example Local
|
82
|
+
# @example Local scope
|
61
83
|
# class A
|
62
84
|
# def initialize
|
63
85
|
# @foo = 1
|
@@ -83,20 +105,45 @@ class SystemNavigation
|
|
83
105
|
# end
|
84
106
|
#
|
85
107
|
# sn.all_accesses(to: :@foo, only_get: true)
|
86
|
-
# #=> [#<UnboundMethod: B#
|
108
|
+
# #=> [#<UnboundMethod: B#initialize>]
|
109
|
+
#
|
110
|
+
# @example Only set invokations
|
111
|
+
# class A
|
112
|
+
# def initialize
|
113
|
+
# @foo = 1
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# class B
|
118
|
+
# attr_reader :foo
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# sn.all_accesses(to: :@foo, only_set: true)
|
122
|
+
# #=> [#<UnboundMethod: A#initialize>]
|
87
123
|
#
|
88
|
-
# @
|
124
|
+
# @example Accesses to global variables
|
125
|
+
# sn.all_accesses(to: :$DEBUG)
|
126
|
+
# #=> [#<UnboundMethod: Gem::Specification#inspect>, ...]
|
127
|
+
#
|
128
|
+
# @example Accesses to class variables
|
129
|
+
# sn.all_accesses(to: :@@required_attributes)
|
130
|
+
# #=> [#<UnboundMethod: Gem::Specification#validate>, ...]
|
131
|
+
#
|
132
|
+
# @param to [Symbol] The name of the instance/global/class variable to search
|
133
|
+
# for
|
89
134
|
# @param from [Class] The class that limits the scope of the query. Optional.
|
90
135
|
# If omitted, performs the query starting from the top of the object
|
91
136
|
# hierarchy (BasicObject)
|
92
137
|
# @param only_get [Boolean] Limits the scope of the query only to methods that
|
93
|
-
# write into the +
|
138
|
+
# write into the +var+. Optional. Mutually exclusive with +only_set+
|
94
139
|
# @param only_set [Boolean] Limits the scope of the query only to methods that
|
95
|
-
# read from the +
|
96
|
-
# @return [Array<UnboundMethod>] methods that access the +
|
140
|
+
# read from the +var+. Optional. Mutually exclusive with +only_get+
|
141
|
+
# @return [Array<UnboundMethod>] methods that access the +var+ according to
|
97
142
|
# the given scope
|
98
|
-
# @
|
99
|
-
#
|
143
|
+
# @raise [ArgumentError] if both +:only_get+ and +:only_set+ were provided
|
144
|
+
# @raise [TypeError] if +:from+ is not a class
|
145
|
+
# @raise [ArgumentError] if +:to+ is not a Symbol representing either of
|
146
|
+
# these: class variable, instance variable, global variable
|
100
147
|
def all_accesses(to:, from: nil, only_get: nil, only_set: nil)
|
101
148
|
if only_set && only_get
|
102
149
|
fail ArgumentError, 'both only_get and only_set were provided'
|
@@ -106,15 +153,32 @@ class SystemNavigation
|
|
106
153
|
fail TypeError, "from must be a Class (#{from.class} given)"
|
107
154
|
end
|
108
155
|
|
156
|
+
unless to.match(VAR_TEMPLATE)
|
157
|
+
fail ArgumentError, 'invalid argument for the `to:` attribute'
|
158
|
+
end
|
159
|
+
|
109
160
|
(from || BasicObject).with_all_sub_and_superclasses.flat_map do |klass|
|
110
161
|
klass.select_methods_that_access(to, only_get, only_set)
|
111
162
|
end
|
112
163
|
end
|
113
164
|
|
114
165
|
##
|
115
|
-
# Query methods for literals they call.
|
166
|
+
# Query methods for literals they call. The supported literals:
|
167
|
+
# * Hashes (only simple Hashes that consist of literals itself)
|
168
|
+
# * Arrays (only simple Arrays that consist of literals itself)
|
169
|
+
# * +true+, +false+ and +nil+
|
170
|
+
# * Integers (same Integers represented with different notations are treated
|
171
|
+
# as the same number)
|
172
|
+
# * Floats
|
173
|
+
# * Strings
|
174
|
+
# * Ranges
|
116
175
|
#
|
117
|
-
# @
|
176
|
+
# @note This is a very costly operation, if you don't provide the +from+
|
177
|
+
# argument
|
178
|
+
# @note The list of supported literals can be found here:
|
179
|
+
# http://ruby-doc.org/core-2.2.2/doc/syntax/literals_rdoc.html
|
180
|
+
#
|
181
|
+
# @example Global scope (every behaviour in this process)
|
118
182
|
# class A
|
119
183
|
# def foo
|
120
184
|
# :hello
|
@@ -130,7 +194,7 @@ class SystemNavigation
|
|
130
194
|
# sn.all_calls(on: :hello)
|
131
195
|
# #=> [#<UnboundMethod: A#foo>, #<UnboundMethod: B#bar>]
|
132
196
|
#
|
133
|
-
# @example Local
|
197
|
+
# @example Local scope
|
134
198
|
# class A
|
135
199
|
# def foo
|
136
200
|
# :hello
|
@@ -158,10 +222,7 @@ class SystemNavigation
|
|
158
222
|
# @param gem [String] Limits the scope of the query only to methods
|
159
223
|
# that are defined in the RubyGem +gem+ classes and modules. Optional.
|
160
224
|
# @return [Array<UnboundMethod>] methods that call the given +literal+
|
161
|
-
# @
|
162
|
-
# argument
|
163
|
-
# @note The list of supported literals can be found here:
|
164
|
-
# http://ruby-doc.org/core-2.2.2/doc/syntax/literals_rdoc.html
|
225
|
+
# @raise [ArgumentError] if both keys (+from:+ and +gem:+) are given
|
165
226
|
def all_calls(on:, from: nil, gem: nil)
|
166
227
|
if from && gem
|
167
228
|
fail ArgumentError, 'both from and gem were provided'
|
@@ -179,7 +240,7 @@ class SystemNavigation
|
|
179
240
|
end
|
180
241
|
|
181
242
|
##
|
182
|
-
# Query classes for
|
243
|
+
# Query classes for methods they implement.
|
183
244
|
#
|
184
245
|
# @example
|
185
246
|
# sn.all_classes_implementing(:~)
|
@@ -223,6 +284,9 @@ class SystemNavigation
|
|
223
284
|
##
|
224
285
|
# Query gems for classes they implement.
|
225
286
|
#
|
287
|
+
# @note This method is not precise. If a class/method in the given gem does
|
288
|
+
# not implement any methods, it won't be included in the result.
|
289
|
+
#
|
226
290
|
# @example
|
227
291
|
# sn.all_classes_in_gem_named('system_navigation')
|
228
292
|
# #=> [SystemNavigation::AncestorMethodFinder, ..., SystemNavigation]
|
@@ -237,6 +301,9 @@ class SystemNavigation
|
|
237
301
|
##
|
238
302
|
# Query gems for modules they implement.
|
239
303
|
#
|
304
|
+
# @note This method is not precise. If a class/method in the given gem does
|
305
|
+
# not implement any methods, it won't be included in the result.
|
306
|
+
#
|
240
307
|
# @example
|
241
308
|
# sn.all_modules_in_gem_named('pry-theme')
|
242
309
|
# #=> [PryTheme::Theme::DefaultAttrs, ..., PryTheme]
|
@@ -250,6 +317,9 @@ class SystemNavigation
|
|
250
317
|
##
|
251
318
|
# Query gems for classes and modules they implement.
|
252
319
|
#
|
320
|
+
# @note This method is not precise. If a class/method in the given gem does
|
321
|
+
# not implement any methods, it won't be included in the result.
|
322
|
+
#
|
253
323
|
# @example
|
254
324
|
# sn.all_classes_and_modules_in_gem_named('pry-theme')
|
255
325
|
# #=> [PryTheme::Preview, ..., PryTheme::Color256]
|
@@ -262,7 +332,7 @@ class SystemNavigation
|
|
262
332
|
end
|
263
333
|
|
264
334
|
##
|
265
|
-
# Get all methods defined in current Ruby process.
|
335
|
+
# Get all methods defined in the current Ruby process.
|
266
336
|
#
|
267
337
|
# @example
|
268
338
|
# sn.all_methods
|
@@ -298,9 +368,8 @@ class SystemNavigation
|
|
298
368
|
# end
|
299
369
|
# end
|
300
370
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
# #=> [#<UnboundMethod: B#bar>, #<UnboundMethod: A#foo>, #<UnboundMethod: M#foo>]
|
371
|
+
# sn.all_methods_with_source(string: 'hello_hi')
|
372
|
+
# #=> [#<UnboundMethod: B#bar>, #<UnboundMethod: A#foo>, #<UnboundMethod: M#foo>]
|
304
373
|
#
|
305
374
|
# @param string [String] The string to be searched for
|
306
375
|
# @param match_case [Boolean] Whether to match case or not. Optional
|
@@ -4,6 +4,12 @@ class SystemNavigation
|
|
4
4
|
self.new(method).compile
|
5
5
|
end
|
6
6
|
|
7
|
+
IVAR = /\A@[^@]/
|
8
|
+
CVAR = /\A@@/
|
9
|
+
GVAR = /\A\$/
|
10
|
+
|
11
|
+
attr_reader :source
|
12
|
+
|
7
13
|
def initialize(method)
|
8
14
|
@method = method
|
9
15
|
@scanner = SystemNavigation::InstructionStream.on(method)
|
@@ -47,12 +53,30 @@ class SystemNavigation
|
|
47
53
|
exptree.includes?(literal)
|
48
54
|
end
|
49
55
|
|
50
|
-
def reads_field?(
|
51
|
-
|
56
|
+
def reads_field?(var)
|
57
|
+
case var
|
58
|
+
when IVAR
|
59
|
+
self.scan_for { @decoder.ivar_read_scan(var) }
|
60
|
+
when CVAR
|
61
|
+
self.scan_for { @decoder.cvar_read_scan(var) }
|
62
|
+
when GVAR
|
63
|
+
self.scan_for { @decoder.gvar_read_scan(var) }
|
64
|
+
else
|
65
|
+
raise ArgumentError, "unknown argument #{var}"
|
66
|
+
end
|
52
67
|
end
|
53
68
|
|
54
|
-
def writes_field?(
|
55
|
-
|
69
|
+
def writes_field?(var)
|
70
|
+
case var
|
71
|
+
when IVAR
|
72
|
+
self.scan_for { @decoder.ivar_write_scan(var) }
|
73
|
+
when CVAR
|
74
|
+
self.scan_for { @decoder.cvar_write_scan(var) }
|
75
|
+
when GVAR
|
76
|
+
self.scan_for { @decoder.gvar_write_scan(var) }
|
77
|
+
else
|
78
|
+
raise ArgumentError, "unknown argument #{var}"
|
79
|
+
end
|
56
80
|
end
|
57
81
|
|
58
82
|
def sends_message?(message)
|
@@ -42,6 +42,30 @@ class SystemNavigation
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def gvar_read_scan(gvar)
|
46
|
+
self.select_instructions(literal: gvar) do |_prev_prev, prev, instruction|
|
47
|
+
next instruction if instruction.reads_gvar?(gvar)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def gvar_write_scan(gvar)
|
52
|
+
self.select_instructions(literal: gvar) do |prev_prev, prev, instruction|
|
53
|
+
next instruction if instruction.writes_gvar?(gvar)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def cvar_read_scan(cvar)
|
58
|
+
self.select_instructions(literal: cvar) do |_prev_prev, prev, instruction|
|
59
|
+
next instruction if instruction.reads_cvar?(cvar)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def cvar_write_scan(cvar)
|
64
|
+
self.select_instructions(literal: cvar) do |prev_prev, prev, instruction|
|
65
|
+
next instruction if instruction.writes_cvar?(cvar)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
45
69
|
def literal_scan(literal)
|
46
70
|
name = @scanner.method.original_name
|
47
71
|
|
@@ -14,6 +14,8 @@ class SystemNavigation
|
|
14
14
|
@lineno = nil
|
15
15
|
@op_id = nil
|
16
16
|
@ivar = nil
|
17
|
+
@gvar = nil
|
18
|
+
@cvar = nil
|
17
19
|
@service_instruction = false
|
18
20
|
end
|
19
21
|
|
@@ -25,7 +27,7 @@ class SystemNavigation
|
|
25
27
|
parse_operand
|
26
28
|
parse_lineno
|
27
29
|
parse_op_id
|
28
|
-
|
30
|
+
parse_var
|
29
31
|
|
30
32
|
self
|
31
33
|
end
|
@@ -57,7 +59,7 @@ class SystemNavigation
|
|
57
59
|
elsif @raw.check(%r{/})
|
58
60
|
@operand = @raw.scan(%r{/.*/})
|
59
61
|
else
|
60
|
-
@operand = @raw.scan(/-?[0-9a-zA-Z:@_
|
62
|
+
@operand = @raw.scan(/-?[0-9a-zA-Z:@_=.$]+/)
|
61
63
|
|
62
64
|
if @raw.peek(1) == ','
|
63
65
|
@operand << @raw.scan(/[^\(]*/).rstrip
|
@@ -83,18 +85,51 @@ class SystemNavigation
|
|
83
85
|
callinfo.terminate
|
84
86
|
end
|
85
87
|
|
88
|
+
def parse_var
|
89
|
+
parse_ivar
|
90
|
+
parse_gvar
|
91
|
+
parse_cvar
|
92
|
+
end
|
93
|
+
|
86
94
|
def parse_ivar
|
87
95
|
return unless accessing_ivar?
|
88
96
|
|
89
97
|
ivar = StringScanner.new(@operand)
|
90
98
|
@ivar = ivar.scan(/:[^,]+/)[1..-1].to_sym
|
91
99
|
ivar.terminate
|
100
|
+
@ivar
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_gvar
|
104
|
+
return unless accessing_gvar?
|
105
|
+
|
106
|
+
gvar = StringScanner.new(@operand)
|
107
|
+
@gvar = gvar.scan(/[^,]+/).to_sym
|
108
|
+
gvar.terminate
|
109
|
+
@gvar
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_cvar
|
113
|
+
return unless accessing_cvar?
|
114
|
+
|
115
|
+
cvar = StringScanner.new(@operand)
|
116
|
+
@cvar = cvar.scan(/:[^,]+/)[1..-1].to_sym
|
117
|
+
cvar.terminate
|
118
|
+
@cvar
|
92
119
|
end
|
93
120
|
|
94
121
|
def accessing_ivar?
|
95
122
|
@opcode == 'getinstancevariable' || @opcode == 'setinstancevariable'
|
96
123
|
end
|
97
124
|
|
125
|
+
def accessing_gvar?
|
126
|
+
@opcode == 'getglobal' || @opcode == 'setglobal'
|
127
|
+
end
|
128
|
+
|
129
|
+
def accessing_cvar?
|
130
|
+
@opcode == 'getclassvariable' || @opcode == 'setclassvariable'
|
131
|
+
end
|
132
|
+
|
98
133
|
def vm_operative?
|
99
134
|
@service_instruction == false
|
100
135
|
end
|
@@ -108,19 +143,35 @@ class SystemNavigation
|
|
108
143
|
end
|
109
144
|
|
110
145
|
def dynamically_reads_ivar?
|
111
|
-
|
146
|
+
self.op_id == 'instance_variable_get'
|
112
147
|
end
|
113
148
|
|
114
149
|
def dynamically_writes_ivar?
|
115
150
|
@op_id == 'instance_variable_set'
|
116
151
|
end
|
117
152
|
|
153
|
+
def reads_gvar?(gvar)
|
154
|
+
@opcode == 'getglobal' && @gvar == gvar
|
155
|
+
end
|
156
|
+
|
157
|
+
def writes_gvar?(gvar)
|
158
|
+
@opcode == 'setglobal' && @gvar == gvar
|
159
|
+
end
|
160
|
+
|
161
|
+
def reads_cvar?(cvar)
|
162
|
+
@opcode == 'getclassvariable' && @cvar == cvar
|
163
|
+
end
|
164
|
+
|
165
|
+
def writes_cvar?(cvar)
|
166
|
+
@opcode == 'setclassvariable' && @cvar == cvar
|
167
|
+
end
|
168
|
+
|
118
169
|
def evals?
|
119
|
-
|
170
|
+
self.op_id == 'eval'
|
120
171
|
end
|
121
172
|
|
122
173
|
def putstrings?(str)
|
123
|
-
return false unless
|
174
|
+
return false unless self.opcode == 'putstring'
|
124
175
|
|
125
176
|
s = str.inspect
|
126
177
|
|
@@ -137,29 +188,32 @@ class SystemNavigation
|
|
137
188
|
def putobjects?(str)
|
138
189
|
return false unless @opcode == 'putobject'
|
139
190
|
|
140
|
-
|
191
|
+
s = (str.instance_of?(String) ? Regexp.escape(str) : str)
|
192
|
+
|
193
|
+
return true if @operand.match(/(?::#{s}\z|\[.*:#{s},.*\])/)
|
141
194
|
return true if @operand == str.inspect
|
142
195
|
|
143
196
|
false
|
144
197
|
end
|
145
198
|
|
146
199
|
def putnils?(str)
|
147
|
-
return false unless
|
200
|
+
return false unless self.opcode == 'putnil'
|
148
201
|
@operand == str.inspect
|
149
202
|
end
|
150
203
|
|
151
204
|
def duparrays?(str)
|
152
|
-
|
205
|
+
s = case str
|
206
|
+
when Array, Hash then Regexp.escape(str.inspect)
|
207
|
+
else str
|
208
|
+
end
|
209
|
+
|
210
|
+
!!(self.opcode == 'duparray' && @operand.match(/:#{s}[,\]]/))
|
153
211
|
end
|
154
212
|
|
155
213
|
def sends_msg?(message)
|
156
214
|
!!(sending? && @op_id == message.to_s)
|
157
215
|
end
|
158
216
|
|
159
|
-
def operand
|
160
|
-
@operand
|
161
|
-
end
|
162
|
-
|
163
217
|
def evaling_str
|
164
218
|
@evaling_str ||= @operand.sub!(/\A"(.+)"/, '\1')
|
165
219
|
end
|
@@ -175,6 +229,10 @@ class SystemNavigation
|
|
175
229
|
def sending?
|
176
230
|
@opcode == 'opt_send_without_block' || @opcode == 'send'
|
177
231
|
end
|
232
|
+
|
233
|
+
protected
|
234
|
+
|
235
|
+
attr_reader :opcode, :op_id
|
178
236
|
end
|
179
237
|
end
|
180
238
|
end
|
@@ -44,17 +44,17 @@ class SystemNavigation
|
|
44
44
|
).as_array.uniq.count == 1
|
45
45
|
end
|
46
46
|
|
47
|
-
def find_accessing_methods(
|
47
|
+
def find_accessing_methods(var:, only_set:, only_get:)
|
48
48
|
self.instance_and_singleton_do(
|
49
49
|
for_all: proc { |_scope, _selectors, method|
|
50
50
|
compiled_method = CompiledMethod.compile(method)
|
51
51
|
if only_set
|
52
|
-
compiled_method.unwrap if compiled_method.writes_field?(
|
52
|
+
compiled_method.unwrap if compiled_method.writes_field?(var)
|
53
53
|
elsif only_get
|
54
|
-
compiled_method.unwrap if compiled_method.reads_field?(
|
54
|
+
compiled_method.unwrap if compiled_method.reads_field?(var)
|
55
55
|
else
|
56
|
-
if compiled_method.reads_field?(
|
57
|
-
compiled_method.writes_field?(
|
56
|
+
if compiled_method.reads_field?(var) ||
|
57
|
+
compiled_method.writes_field?(var)
|
58
58
|
compiled_method.unwrap
|
59
59
|
end
|
60
60
|
end
|
@@ -35,7 +35,7 @@ class SystemNavigation
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def select_methods_that_access(
|
38
|
+
def select_methods_that_access(var, only_get, only_set)
|
39
39
|
own_methods = self.own_methods
|
40
40
|
if ancestor_methods.any?
|
41
41
|
ancestor_methods.each do |methods|
|
@@ -48,7 +48,7 @@ class SystemNavigation
|
|
48
48
|
MethodQuery.execute(
|
49
49
|
collection: own_methods,
|
50
50
|
query: :find_accessing_methods,
|
51
|
-
|
51
|
+
var: var,
|
52
52
|
only_get: only_get,
|
53
53
|
only_set: only_set,
|
54
54
|
behavior: self).as_array
|
@@ -207,10 +207,10 @@ class SystemNavigation
|
|
207
207
|
query: :select_sent_messages)
|
208
208
|
end
|
209
209
|
|
210
|
-
def which_selectors_store_into(
|
210
|
+
def which_selectors_store_into(var)
|
211
211
|
self.selectors.select do |sel|
|
212
212
|
meth = self.instance_method(sel)
|
213
|
-
meth.writes_field?(
|
213
|
+
meth.writes_field?(var)
|
214
214
|
end
|
215
215
|
end
|
216
216
|
|
@@ -1,6 +1,12 @@
|
|
1
1
|
class SystemNavigation
|
2
|
+
# @api private
|
3
|
+
# @since 0.1.0
|
2
4
|
class RubyEnvironment
|
3
|
-
|
5
|
+
##
|
6
|
+
# Execute +block+ on each class, metaclass, module and module's metaclass.
|
7
|
+
#
|
8
|
+
# @return [Enumerator] if +block+ was given
|
9
|
+
# @return [Enumerator] if +block+ is missing
|
4
10
|
def all_behaviors(&block)
|
5
11
|
enum = Enumerator.new do |y|
|
6
12
|
ObjectSpace.each_object(Module) do |klass|
|
@@ -16,6 +22,11 @@ class SystemNavigation
|
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
25
|
+
##
|
26
|
+
# Execute +block+ on each class (but not its metaclass).
|
27
|
+
#
|
28
|
+
# @return [Enumerator] if +block+ was given
|
29
|
+
# @return [Enumerator] if +block+ is missing
|
19
30
|
def all_classes(&block)
|
20
31
|
enum = Enumerator.new do |y|
|
21
32
|
ObjectSpace.each_object(Class) do |klass|
|
@@ -30,6 +41,11 @@ class SystemNavigation
|
|
30
41
|
end
|
31
42
|
end
|
32
43
|
|
44
|
+
##
|
45
|
+
# Execute +block+ on each class and module (but not their metaclasses).
|
46
|
+
#
|
47
|
+
# @return [Enumerator] if +block+ was given
|
48
|
+
# @return [Enumerator] if +block+ is missing
|
33
49
|
def all_classes_and_modules(&block)
|
34
50
|
enum = Enumerator.new do |y|
|
35
51
|
ObjectSpace.each_object(Module) do |klass|
|
@@ -44,6 +60,11 @@ class SystemNavigation
|
|
44
60
|
end
|
45
61
|
end
|
46
62
|
|
63
|
+
##
|
64
|
+
# Execute +block+ on each module (but not its metaclass).
|
65
|
+
#
|
66
|
+
# @return [Enumerator] if +block+ was given
|
67
|
+
# @return [Enumerator] if +block+ is missing
|
47
68
|
def all_modules(&block)
|
48
69
|
enum = Enumerator.new do |y|
|
49
70
|
self.all_classes_and_modules.each do |klass|
|
@@ -58,10 +79,15 @@ class SystemNavigation
|
|
58
79
|
end
|
59
80
|
end
|
60
81
|
|
82
|
+
##
|
83
|
+
# Execute +block+ on each object.
|
84
|
+
#
|
85
|
+
# @return [Enumerator] if +block+ was given
|
86
|
+
# @return [Enumerator] if +block+ is missing
|
61
87
|
def all_objects(&block)
|
62
88
|
enum = Enumerator.new do |y|
|
63
|
-
ObjectSpace.each_object do |
|
64
|
-
y.yield
|
89
|
+
ObjectSpace.each_object do |obj|
|
90
|
+
y.yield obj
|
65
91
|
end
|
66
92
|
end
|
67
93
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: system_navigation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyrylo Silin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fast_method_source
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|