system_navigation 0.1.0 → 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 +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
|