system_navigation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENCE.txt +19 -0
- data/README.md +70 -0
- data/VERSION +1 -0
- data/lib/system_navigation.rb +374 -0
- data/lib/system_navigation/ancestor_method_finder.rb +44 -0
- data/lib/system_navigation/array_refinement.rb +21 -0
- data/lib/system_navigation/compiled_method.rb +94 -0
- data/lib/system_navigation/expression_tree.rb +95 -0
- data/lib/system_navigation/instruction_stream.rb +33 -0
- data/lib/system_navigation/instruction_stream/decoder.rb +91 -0
- data/lib/system_navigation/instruction_stream/instruction.rb +180 -0
- data/lib/system_navigation/instruction_stream/instruction/attr_instruction.rb +90 -0
- data/lib/system_navigation/method_hash.rb +49 -0
- data/lib/system_navigation/method_query.rb +98 -0
- data/lib/system_navigation/module_refinement.rb +226 -0
- data/lib/system_navigation/ruby_environment.rb +75 -0
- metadata +117 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class InstructionStream
|
3
|
+
class Instruction
|
4
|
+
class AttrInstruction < Instruction
|
5
|
+
def self.parse(method, sym)
|
6
|
+
instr = self.new(method)
|
7
|
+
self.attrreaderinstr(instr, sym) || self.attrwriterinstr(instr, sym)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.attrreaderinstr(instr, sym)
|
11
|
+
instr.accept(AttrReaderInstruction.new(sym)) && instr.parse
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.attrwriterinstr(instr, sym)
|
15
|
+
instr.accept(AttrWriterInstruction.new(sym)) && instr.parse
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :visitor
|
19
|
+
attr_reader :method
|
20
|
+
|
21
|
+
def initialize(method)
|
22
|
+
@method = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def accept(visitor)
|
26
|
+
visitor.visit(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse
|
30
|
+
[self.visitor]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def convert_accessor_to_name(sym)
|
36
|
+
sym.to_s.tr('@', '').downcase
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AttrReaderInstruction < AttrInstruction
|
41
|
+
def initialize(sym)
|
42
|
+
@sym = sym
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit(obj)
|
46
|
+
matched = obj.method.original_name.to_s == convert_accessor_to_name(@sym)
|
47
|
+
obj.visitor = self if matched
|
48
|
+
matched
|
49
|
+
end
|
50
|
+
|
51
|
+
def reads_ivar?(_sym)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def writes_ivar?(_sym)
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def putobjects?(sym)
|
60
|
+
@sym == sym
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class AttrWriterInstruction < AttrInstruction
|
65
|
+
def initialize(sym)
|
66
|
+
@sym = sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit(obj)
|
70
|
+
name = obj.method.original_name.to_s
|
71
|
+
matched = (name[-1] == '=') && (name[0..-2] == convert_accessor_to_name(@sym))
|
72
|
+
obj.visitor = self if matched
|
73
|
+
matched
|
74
|
+
end
|
75
|
+
|
76
|
+
def reads_ivar?(_sym)
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
def writes_ivar?(_sym)
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def putobjects?(sym)
|
85
|
+
@sym == sym
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class MethodHash < Hash
|
3
|
+
def self.create(**args)
|
4
|
+
self.new(args)
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(based_on: nil, include_super: nil)
|
9
|
+
@hash = super()
|
10
|
+
|
11
|
+
if based_on
|
12
|
+
@hash.merge!({
|
13
|
+
public: {
|
14
|
+
instance: based_on.public_instance_methods(include_super),
|
15
|
+
singleton: based_on.singleton_class.public_instance_methods(include_super)
|
16
|
+
},
|
17
|
+
private: {
|
18
|
+
instance: based_on.private_instance_methods(include_super),
|
19
|
+
singleton: based_on.singleton_class.private_instance_methods(include_super)
|
20
|
+
},
|
21
|
+
protected: {
|
22
|
+
instance: based_on.protected_instance_methods(include_super),
|
23
|
+
singleton: based_on.singleton_class.protected_instance_methods(include_super)
|
24
|
+
}
|
25
|
+
})
|
26
|
+
else
|
27
|
+
@hash.merge!(empty_hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_array
|
32
|
+
self.values.map { |h| h[:instance] + h[:singleton] }.flatten.compact
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
self == empty_hash
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def empty_hash
|
42
|
+
{
|
43
|
+
public: {instance: [], singleton: []},
|
44
|
+
private: {instance: [], singleton: []},
|
45
|
+
protected: {instance: [], singleton: []}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class MethodQuery
|
3
|
+
def self.execute(collection:, query:, behavior: nil, **rest)
|
4
|
+
args = [query]
|
5
|
+
args << rest unless rest.empty?
|
6
|
+
self.new(collection, behavior).__send__(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(collection, behavior)
|
10
|
+
@collection = collection
|
11
|
+
@behavior = behavior
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert_to_methods
|
15
|
+
self.instance_and_singleton_do(
|
16
|
+
for_instance: proc { |_scope, _selectors, selector|
|
17
|
+
@behavior.instance_method(selector)
|
18
|
+
},
|
19
|
+
|
20
|
+
for_singleton: proc { |_scope, _selectors, selector|
|
21
|
+
@behavior.singleton_class.instance_method(selector)
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def tupleize
|
27
|
+
self.instance_and_singleton_do(
|
28
|
+
for_all: proc { |_scope, _selectors, method| [method.name, method] }
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_literal(literal:)
|
33
|
+
self.instance_and_singleton_do(
|
34
|
+
for_all: proc { |_scope, _selectors, method|
|
35
|
+
compiled_method = CompiledMethod.compile(method)
|
36
|
+
compiled_method.unwrap if compiled_method.has_literal?(literal)
|
37
|
+
}
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def all_in_the_same_file?
|
42
|
+
self.instance_and_singleton_do(
|
43
|
+
for_all: proc { |_scope, _selectors, method| method.source_location[0] }
|
44
|
+
).as_array.uniq.count == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_accessing_methods(ivar:, only_set:, only_get:)
|
48
|
+
self.instance_and_singleton_do(
|
49
|
+
for_all: proc { |_scope, _selectors, method|
|
50
|
+
compiled_method = CompiledMethod.compile(method)
|
51
|
+
if only_set
|
52
|
+
compiled_method.unwrap if compiled_method.writes_field?(ivar)
|
53
|
+
elsif only_get
|
54
|
+
compiled_method.unwrap if compiled_method.reads_field?(ivar)
|
55
|
+
else
|
56
|
+
if compiled_method.reads_field?(ivar) ||
|
57
|
+
compiled_method.writes_field?(ivar)
|
58
|
+
compiled_method.unwrap
|
59
|
+
end
|
60
|
+
end
|
61
|
+
}
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def select_sent_messages
|
66
|
+
self.instance_and_singleton_do(
|
67
|
+
for_all: proc { |_scope, _selectors, method|
|
68
|
+
compiled_method = CompiledMethod.compile(method)
|
69
|
+
compiled_method.sent_messages.uniq.map(&:to_sym)
|
70
|
+
}
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def instance_and_singleton_do(for_instance: nil, for_singleton: nil, for_all: nil)
|
77
|
+
for_instance = for_all && for_singleton = for_all if for_all
|
78
|
+
|
79
|
+
@collection.inject(MethodHash.new) do |h, (scope, selectors)|
|
80
|
+
self.evaluate(callable: for_instance, group: :instance, hash: h,
|
81
|
+
scope: scope, selectors: selectors)
|
82
|
+
self.evaluate(callable: for_singleton, group: :singleton, hash: h,
|
83
|
+
scope: scope, selectors: selectors)
|
84
|
+
h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def evaluate(callable:, group:, hash:, scope:, selectors:)
|
89
|
+
return if callable.nil?
|
90
|
+
|
91
|
+
result = selectors[group].map do |selector|
|
92
|
+
callable.call(scope, selectors, selector)
|
93
|
+
end
|
94
|
+
|
95
|
+
hash[scope][group].concat(result)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
module ModuleRefinement
|
3
|
+
refine Module do
|
4
|
+
def with_all_sub_and_superclasses
|
5
|
+
Enumerator.new do |y|
|
6
|
+
self.with_all_subclasses.each { |klass| y << klass }
|
7
|
+
self.with_all_superclasses.each { |klass| y << klass }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_all_subclasses
|
12
|
+
Enumerator.new do |y|
|
13
|
+
self.all_subclasses.push(self).each { |subclass| y << subclass }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def all_subclasses
|
18
|
+
all_subclasses = []
|
19
|
+
|
20
|
+
ObjectSpace.each_object(self.singleton_class) do |klass|
|
21
|
+
all_subclasses.unshift(klass) if klass != self
|
22
|
+
end
|
23
|
+
|
24
|
+
all_subclasses
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_all_superclasses
|
28
|
+
if self.superclass
|
29
|
+
Enumerator.new do |y|
|
30
|
+
y.yield self.superclass
|
31
|
+
self.superclass.with_all_superclasses.each { |klass| y << klass }
|
32
|
+
end
|
33
|
+
else
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def select_methods_that_access(ivar, only_get, only_set)
|
39
|
+
own_methods = self.own_methods
|
40
|
+
if ancestor_methods.any?
|
41
|
+
ancestor_methods.each do |methods|
|
42
|
+
own_methods.merge!(methods) do |_group, old_h, new_h|
|
43
|
+
old_h.merge!(new_h) { |_key, oldval, newval| oldval | newval }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
MethodQuery.execute(
|
49
|
+
collection: own_methods,
|
50
|
+
query: :find_accessing_methods,
|
51
|
+
ivar: ivar,
|
52
|
+
only_get: only_get,
|
53
|
+
only_set: only_set,
|
54
|
+
behavior: self).as_array
|
55
|
+
end
|
56
|
+
|
57
|
+
def select_methods_that_refer_to(literal)
|
58
|
+
MethodQuery.execute(
|
59
|
+
collection: self.own_methods,
|
60
|
+
query: :find_literal,
|
61
|
+
literal: literal,
|
62
|
+
behavior: self).as_array
|
63
|
+
end
|
64
|
+
|
65
|
+
def reachable_selectors
|
66
|
+
MethodHash.create(based_on: self, include_super: true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def own_selectors
|
70
|
+
MethodHash.create(based_on: self, include_super: false)
|
71
|
+
end
|
72
|
+
|
73
|
+
def reachable_methods
|
74
|
+
MethodQuery.execute(
|
75
|
+
collection: self.reachable_selectors,
|
76
|
+
query: :convert_to_methods,
|
77
|
+
behavior: self)
|
78
|
+
end
|
79
|
+
|
80
|
+
def own_methods
|
81
|
+
MethodQuery.execute(
|
82
|
+
collection: self.own_selectors,
|
83
|
+
query: :convert_to_methods,
|
84
|
+
behavior: self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def reachable_method_hash
|
88
|
+
MethodQuery.execute(
|
89
|
+
collection: self.reachable_methods,
|
90
|
+
query: :tupleize)
|
91
|
+
end
|
92
|
+
|
93
|
+
def own_method_hash
|
94
|
+
MethodQuery.execute(
|
95
|
+
collection: self.own_methods,
|
96
|
+
query: :tupleize)
|
97
|
+
end
|
98
|
+
|
99
|
+
def which_global_selectors_refer_to(literal)
|
100
|
+
who = []
|
101
|
+
|
102
|
+
self.own_selectors_and_methods do |selector, method|
|
103
|
+
if method.has_literal?(literal)
|
104
|
+
who << selector
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
who
|
109
|
+
end
|
110
|
+
|
111
|
+
def belongs_to?(gem_name)
|
112
|
+
gemspec = Gem::Specification.find_all_by_name(gem_name).last
|
113
|
+
|
114
|
+
return false if gemspec.nil? || self.own_selectors.empty?
|
115
|
+
|
116
|
+
pattern = %r{(?:/gems/#{gem_name}-#{gemspec.version}/)|(?:/lib/ruby/[[0-9]\.]+/#{gem_name}/)}
|
117
|
+
match_location = proc { |locations|
|
118
|
+
!!locations.max_by { |_k, value| value }[0].match(pattern)
|
119
|
+
}
|
120
|
+
|
121
|
+
if self.contains_only_rb_methods?
|
122
|
+
if self.all_neighbour_methods?
|
123
|
+
self.own_methods.as_array.all? do |method|
|
124
|
+
method.source_location.first.match(pattern)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
grouped_locations = self.group_locations_by_path
|
128
|
+
return false if grouped_locations.empty?
|
129
|
+
|
130
|
+
if grouped_locations.all? { |l| l[0].match(pattern) }
|
131
|
+
true
|
132
|
+
else
|
133
|
+
match_location.call(grouped_locations)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
else
|
137
|
+
grouped_locations = self.group_locations_by_path
|
138
|
+
grouped_locations.delete_if { |k, v| k.nil? }
|
139
|
+
|
140
|
+
if grouped_locations.empty?
|
141
|
+
false
|
142
|
+
else
|
143
|
+
match_location.call(grouped_locations)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def contains_only_rb_methods?
|
149
|
+
self.own_methods.as_array.all? { |method| method.source_location }
|
150
|
+
end
|
151
|
+
|
152
|
+
def all_neighbour_methods?
|
153
|
+
MethodQuery.execute(
|
154
|
+
collection: self.own_methods,
|
155
|
+
query: :all_in_the_same_file?)
|
156
|
+
end
|
157
|
+
|
158
|
+
def group_locations_by_path
|
159
|
+
Hash[
|
160
|
+
self.own_methods.as_array.map do |method|
|
161
|
+
method.source_location && method.source_location.first || nil
|
162
|
+
end.group_by(&:itself).map do |key, value|
|
163
|
+
[key, value.count]
|
164
|
+
end.reject { |k, _v| k.nil? }
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
def select_matching_methods(string, match_case)
|
169
|
+
self.own_methods.as_array.select do |method|
|
170
|
+
compiled_method = CompiledMethod.compile(method)
|
171
|
+
if compiled_method.source_contains?(string, match_case)
|
172
|
+
compiled_method.unwrap
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def select_c_methods
|
178
|
+
self.own_methods.as_array.select do |method|
|
179
|
+
compiled_method = CompiledMethod.compile(method)
|
180
|
+
if compiled_method.c_method?
|
181
|
+
compiled_method.unwrap
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def select_rb_methods
|
187
|
+
self.own_methods.as_array.select do |method|
|
188
|
+
compiled_method = CompiledMethod.compile(method)
|
189
|
+
if compiled_method.rb_method?
|
190
|
+
compiled_method.unwrap
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def select_senders_of(message)
|
196
|
+
self.own_methods.as_array.select do |method|
|
197
|
+
compiled_method = CompiledMethod.compile(method)
|
198
|
+
if compiled_method.sends_message?(message)
|
199
|
+
compiled_method.unwrap
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def all_messages
|
205
|
+
MethodQuery.execute(
|
206
|
+
collection: self.own_methods,
|
207
|
+
query: :select_sent_messages)
|
208
|
+
end
|
209
|
+
|
210
|
+
def which_selectors_store_into(ivar)
|
211
|
+
self.selectors.select do |sel|
|
212
|
+
meth = self.instance_method(sel)
|
213
|
+
meth.writes_field?(ivar)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def ancestor_methods
|
218
|
+
AncestorMethodFinder.find_all_ancestors(of: self)
|
219
|
+
end
|
220
|
+
|
221
|
+
def includes_selector?(selector)
|
222
|
+
self.own_selectors.as_array.include?(selector)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|