mirrors 0.0.1
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 +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +59 -0
- data/README.md +1 -0
- data/asdfasdf.rb +53 -0
- data/bin/bundler +17 -0
- data/bin/byebug +17 -0
- data/bin/testunit +8 -0
- data/circle.yml +6 -0
- data/dev.yml +6 -0
- data/lib/mirrors.rb +150 -0
- data/lib/mirrors/class_mirror.rb +197 -0
- data/lib/mirrors/class_mixin.rb +11 -0
- data/lib/mirrors/field_mirror.rb +24 -0
- data/lib/mirrors/field_mirror/class_variable_mirror.rb +23 -0
- data/lib/mirrors/field_mirror/constant_mirror.rb +34 -0
- data/lib/mirrors/field_mirror/instance_variable_mirror.rb +23 -0
- data/lib/mirrors/hook.rb +33 -0
- data/lib/mirrors/index/indexer.rb +6 -0
- data/lib/mirrors/index/marker.rb +40 -0
- data/lib/mirrors/invoke.rb +29 -0
- data/lib/mirrors/method_mirror.rb +206 -0
- data/lib/mirrors/mirror.rb +37 -0
- data/lib/mirrors/object_mirror.rb +25 -0
- data/lib/mirrors/package_inference.rb +164 -0
- data/lib/mirrors/package_inference/class_to_file_resolver.rb +66 -0
- data/lib/mirrors/package_mirror.rb +33 -0
- data/lib/mirrors/visitors/disasm_visitor.rb +11 -0
- data/lib/mirrors/visitors/iseq_visitor.rb +84 -0
- data/lib/mirrors/visitors/references_visitor.rb +58 -0
- data/lib/mirrors/visitors/yasmdata.rb +212 -0
- data/lol.rb +35 -0
- data/mirrors.gemspec +19 -0
- data/test/fixtures/class.rb +29 -0
- data/test/fixtures/field.rb +9 -0
- data/test/fixtures/method.rb +15 -0
- data/test/fixtures/object.rb +5 -0
- data/test/fixtures/reflect.rb +14 -0
- data/test/mirrors/class_mirror_test.rb +87 -0
- data/test/mirrors/field_mirror_test.rb +125 -0
- data/test/mirrors/iseq_visitor_test.rb +56 -0
- data/test/mirrors/marker_test.rb +48 -0
- data/test/mirrors/method_mirror_test.rb +62 -0
- data/test/mirrors/object_mirror_test.rb +16 -0
- data/test/mirrors/package_inference_test.rb +31 -0
- data/test/mirrors/references_visitor_test.rb +30 -0
- data/test/mirrors_test.rb +38 -0
- data/test/test_helper.rb +12 -0
- metadata +137 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mirrors
|
2
|
+
# A class to reflect on instance, class, and class instance variables,
|
3
|
+
# as well as constants.
|
4
|
+
class FieldMirror < Mirror
|
5
|
+
Field = Struct.new(:object, :name)
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(obj)
|
10
|
+
super
|
11
|
+
@object = obj.object
|
12
|
+
@name = obj.name.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [ClassMirror] The class this method was originally defined in
|
16
|
+
def defining_class
|
17
|
+
Mirrors.reflect(@object)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'mirrors/field_mirror/class_variable_mirror'
|
23
|
+
require 'mirrors/field_mirror/instance_variable_mirror'
|
24
|
+
require 'mirrors/field_mirror/constant_mirror'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mirrors
|
2
|
+
class ClassVariableMirror < FieldMirror
|
3
|
+
def value
|
4
|
+
Mirrors.reflect(@object.class_variable_get(@name))
|
5
|
+
end
|
6
|
+
|
7
|
+
def value=(o)
|
8
|
+
@object.class_variable_set(@name, o)
|
9
|
+
end
|
10
|
+
|
11
|
+
def public?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def protected?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def private?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Mirrors
|
2
|
+
class ConstantMirror < FieldMirror
|
3
|
+
def value
|
4
|
+
if path = @object.autoload?(@name)
|
5
|
+
unless $LOADED_FEATURES.include?(path) ||
|
6
|
+
$LOADED_FEATURES.include?(File.expand_path(path))
|
7
|
+
# Do not trigger autoload
|
8
|
+
return nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
Mirrors.reflect @object.const_get(@name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def value=(o)
|
15
|
+
@object.const_set(@name, o)
|
16
|
+
end
|
17
|
+
|
18
|
+
def public?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def protected?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def private?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete
|
31
|
+
@object.send(:remove_const, @name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mirrors
|
2
|
+
class InstanceVariableMirror < FieldMirror
|
3
|
+
def value
|
4
|
+
Mirrors.reflect(@object.instance_variable_get(@name))
|
5
|
+
end
|
6
|
+
|
7
|
+
def value=(o)
|
8
|
+
@object.instance_variable_set(@name, o)
|
9
|
+
end
|
10
|
+
|
11
|
+
def public?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def protected?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def private?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/mirrors/hook.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'mirrors/invoke'
|
2
|
+
|
3
|
+
module Mirrors
|
4
|
+
CLASS_DEFINITION_POINTS = {}
|
5
|
+
|
6
|
+
NoGemfile = Class.new(StandardError)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :project_root
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
unless Mirrors.project_root
|
15
|
+
appline = caller.detect { |line| line !~ /:in `require'/ }
|
16
|
+
f = File.expand_path(appline.sub(/:\d.*/, ''))
|
17
|
+
Mirrors.project_root = loop do
|
18
|
+
f = File.dirname(f)
|
19
|
+
raise Mirrors::NoGemfile if f == '/'
|
20
|
+
break f if File.exist?("#{f}/Gemfile")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Class
|
26
|
+
alias_method :__lg_orig_inherited, :inherited
|
27
|
+
def inherited(subclass)
|
28
|
+
file = caller[0].sub(/:\d+:in.*/, '')
|
29
|
+
key = Mirrors.module_instance_invoke(subclass, :inspect)
|
30
|
+
Mirrors::CLASS_DEFINITION_POINTS[key] = file
|
31
|
+
__lg_orig_inherited(subclass)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Mirrors
|
2
|
+
class Marker
|
3
|
+
TYPE_TASK = 'mirrors.marker.task'.freeze
|
4
|
+
TYPE_PROBLEM = 'mirrors.marker.problem'.freeze
|
5
|
+
TYPE_TEXT = 'mirrors.marker.text'.freeze
|
6
|
+
TYPE_CLASS_REFERENCE = 'mirrors.marker.text.class_reference'.freeze
|
7
|
+
TYPE_METHOD_REFERENCE = 'mirrors.marker.text.method_reference'.freeze
|
8
|
+
TYPE_FIELD_REFERENCE = 'mirrors.marker.text.field_reference'.freeze
|
9
|
+
|
10
|
+
NO_LINE = -1
|
11
|
+
NO_COLUMN = -1
|
12
|
+
|
13
|
+
attr_reader :message, :type, :file, :line, :start_column, :end_column
|
14
|
+
|
15
|
+
def initialize(type: TYPE_TASK, message: '', file: nil, line: NO_LINE, start_column: NO_COLUMN, end_column: NO_COLUMN)
|
16
|
+
@type = type
|
17
|
+
@message = message
|
18
|
+
@file = file
|
19
|
+
@line = line
|
20
|
+
@start_column = start_column
|
21
|
+
@end_column = end_column
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
type == other.type && message == other.message && line == other.line && start_column == other.start_column && end_column == other.end_column
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self == other
|
30
|
+
end
|
31
|
+
|
32
|
+
def hash
|
33
|
+
[@type, @message, @line, @start_column, @end_column].hash
|
34
|
+
end
|
35
|
+
|
36
|
+
def hashy
|
37
|
+
[@type, @message, @line, @start_column, @end_column]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Mirrors
|
2
|
+
# Methods here follow the pattern of:
|
3
|
+
# <target>_<instance or singleton>_<invoke or method>
|
4
|
+
#
|
5
|
+
# * target is the owner of the method
|
6
|
+
# * instance or singleton indicates whether we want an instance method or a
|
7
|
+
# singleton method from the target
|
8
|
+
# * invoke calls the method on a receiver, which must be compatible with the
|
9
|
+
# method. method returns the method to bind to whatever receiver you want.
|
10
|
+
|
11
|
+
@unbound_module_instance_methods = {}
|
12
|
+
@unbound_class_singleton_methods = {}
|
13
|
+
|
14
|
+
def self.module_instance_invoke(receiver, msg)
|
15
|
+
module_instance_method(msg).bind(receiver).call
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.module_instance_method(msg)
|
19
|
+
@unbound_module_instance_methods[msg] ||= Module.instance_method(msg)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.class_singleton_invoke(receiver, msg)
|
23
|
+
class_singleton_method(msg).bind(receiver).call
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.class_singleton_method(msg)
|
27
|
+
@unbound_class_singleton_methods[msg] ||= Class.method(msg).unbind
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'method_source'
|
2
|
+
require 'base64'
|
3
|
+
require 'ripper'
|
4
|
+
require 'pp'
|
5
|
+
require 'mirrors/visitors/iseq_visitor'
|
6
|
+
require 'mirrors/visitors/references_visitor'
|
7
|
+
|
8
|
+
module Mirrors
|
9
|
+
# A MethodMirror should reflect on methods, but in a more general
|
10
|
+
# sense than the Method and UnboundMethod classes in Ruby are able
|
11
|
+
# to offer.
|
12
|
+
#
|
13
|
+
# In actual execution, a method is pretty much every chunk of code,
|
14
|
+
# even loading a file triggers a process not unlike compiling a
|
15
|
+
# method (if only for the side-effects). Method mirrors should allow
|
16
|
+
# access to the runtime objects, but also to their static
|
17
|
+
# representations (bytecode, source, ...), their debugging
|
18
|
+
# information and statistical information
|
19
|
+
class MethodMirror < Mirror
|
20
|
+
# @return [String, nil] The filename, if available
|
21
|
+
def file
|
22
|
+
sl = source_location
|
23
|
+
sl ? sl.first : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Fixnum, nil] The source line, if available
|
27
|
+
def line
|
28
|
+
sl = source_location
|
29
|
+
sl && sl.last ? sl.last - 1 : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] The method name
|
33
|
+
def selector
|
34
|
+
@subject.name.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [ClassMirror] The class this method was originally defined in
|
38
|
+
def defining_class
|
39
|
+
Mirrors.reflect @subject.send(:owner)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the value the block argument, or nil
|
43
|
+
#
|
44
|
+
# @return [String, nil]
|
45
|
+
def block_argument
|
46
|
+
args(:block).first
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a field mirror with name and possibly value of the splat
|
50
|
+
# argument, or nil, if there is none to this method.
|
51
|
+
#
|
52
|
+
# @return [String, nil]
|
53
|
+
def splat_argument
|
54
|
+
args(:rest).first
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns names and values of the optional arguments.
|
58
|
+
#
|
59
|
+
# @return [Array<String>, nil]
|
60
|
+
def optional_arguments
|
61
|
+
args(:opt)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the name and possibly values of the required arguments
|
65
|
+
#
|
66
|
+
# @return [Array<String>]
|
67
|
+
def required_arguments
|
68
|
+
args(:req)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Queries the method for it's arguments and returns a list of
|
72
|
+
# mirrors that hold name and value information.
|
73
|
+
#
|
74
|
+
# @return [Array<String>]
|
75
|
+
def arguments
|
76
|
+
@subject.send(:parameters).map { |_, a| a.to_s }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Is the method :public, :private, or :protected?
|
80
|
+
#
|
81
|
+
# @return [String]
|
82
|
+
def visibility
|
83
|
+
return :public if visibility?(:public)
|
84
|
+
return :private if visibility?(:private)
|
85
|
+
:protected
|
86
|
+
end
|
87
|
+
|
88
|
+
def protected?
|
89
|
+
visibility?(:protected)
|
90
|
+
end
|
91
|
+
|
92
|
+
def public?
|
93
|
+
visibility?(:public)
|
94
|
+
end
|
95
|
+
|
96
|
+
def private?
|
97
|
+
visibility?(:private)
|
98
|
+
end
|
99
|
+
|
100
|
+
def super_method
|
101
|
+
owner = @subject.send(:owner)
|
102
|
+
|
103
|
+
if owner.is_a?(Class)
|
104
|
+
meth = Mirrors
|
105
|
+
.class_singleton_method(:allocate)
|
106
|
+
.bind(instance)
|
107
|
+
.super_method
|
108
|
+
.unbind
|
109
|
+
else
|
110
|
+
meth = @subject.bind(owner).super_method.unbind
|
111
|
+
end
|
112
|
+
|
113
|
+
meth ? Mirrors.reflect(meth) : nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [String,nil] The source code of this method
|
117
|
+
def source
|
118
|
+
@source ||= unindent(@subject.send(:source))
|
119
|
+
rescue MethodSource::SourceNotFoundError
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [String,nil] The pre-definition comment of this method
|
124
|
+
def comment
|
125
|
+
@subject.send(:comment)
|
126
|
+
rescue MethodSource::SourceNotFoundError
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the instruction sequence for the method (cached)
|
131
|
+
def iseq
|
132
|
+
@iseq ||= RubyVM::InstructionSequence.of(@subject)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the disassembled code if available.
|
136
|
+
#
|
137
|
+
# @return [String, nil] human-readable bytedcode dump
|
138
|
+
def bytecode
|
139
|
+
@bytecode ||= iseq.disasm if iseq
|
140
|
+
@bytecode
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the parse tree if available
|
144
|
+
#
|
145
|
+
# @return [String, nil] prettified AST
|
146
|
+
def sexp
|
147
|
+
src = source
|
148
|
+
src ? Ripper.sexp(src).pretty_inspect : nil
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the compiled code if available.
|
152
|
+
#
|
153
|
+
# @return [RubyVM::InstructionSequence, nil] native code
|
154
|
+
def native_code
|
155
|
+
RubyVM::InstructionSequence.of(@subject)
|
156
|
+
end
|
157
|
+
|
158
|
+
def name
|
159
|
+
@subject.name
|
160
|
+
end
|
161
|
+
|
162
|
+
def references
|
163
|
+
visitor = Mirrors::ReferencesVisitor.new
|
164
|
+
visitor.call(self)
|
165
|
+
visitor.markers
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def visibility?(type)
|
171
|
+
list = @subject.send(:owner).send("#{type}_instance_methods")
|
172
|
+
list.any? { |m| m.to_s == selector }
|
173
|
+
end
|
174
|
+
|
175
|
+
def args(type)
|
176
|
+
args = []
|
177
|
+
@subject.send(:parameters).select { |t, n| args << n.to_s if t == type }
|
178
|
+
args
|
179
|
+
end
|
180
|
+
|
181
|
+
def source_location
|
182
|
+
@subject.send(:source_location)
|
183
|
+
end
|
184
|
+
|
185
|
+
def unindent(str)
|
186
|
+
lines = str.split("\n")
|
187
|
+
return str if lines.empty?
|
188
|
+
|
189
|
+
indents = lines.map do |line|
|
190
|
+
if line =~ /\S/
|
191
|
+
line.start_with?(" ") ? line.match(/^ +/).offset(0)[1] : 0
|
192
|
+
end
|
193
|
+
end
|
194
|
+
indents.compact!
|
195
|
+
|
196
|
+
if indents.empty?
|
197
|
+
# No lines had any non-whitespace characters.
|
198
|
+
return ([""] * lines.size).join "\n"
|
199
|
+
end
|
200
|
+
|
201
|
+
min_indent = indents.min
|
202
|
+
return str if min_indent.zero?
|
203
|
+
lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join("\n")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Mirrors
|
2
|
+
# The basic mirror
|
3
|
+
class Mirror
|
4
|
+
def initialize(obj)
|
5
|
+
@subject = obj
|
6
|
+
end
|
7
|
+
|
8
|
+
def subject_id
|
9
|
+
@subject.__id__.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
# A generic representation of the object under observation.
|
13
|
+
def name
|
14
|
+
if @subject.is_a?(String) || @subject.is_a?(Symbol)
|
15
|
+
@subject
|
16
|
+
else
|
17
|
+
@subject.inspect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# The equivalent to #==/#eql? for comparison of mirrors against objects
|
22
|
+
def mirrors?(other)
|
23
|
+
@subject == other
|
24
|
+
end
|
25
|
+
|
26
|
+
# Accessor to the reflected object
|
27
|
+
def reflectee
|
28
|
+
@subject
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def mirrors(list)
|
34
|
+
list.collect { |e| Mirrors.reflect(e) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|