ruby-decompiler 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.
- data/README.rdoc +38 -0
- data/lib/decompiler/method/as_code.rb +58 -0
- data/lib/decompiler/method/as_expression.rb +33 -0
- data/lib/decompiler/method/origin.rb +29 -0
- data/lib/decompiler/method/signature.rb +147 -0
- data/lib/decompiler/method/signature/argument.rb +102 -0
- data/lib/decompiler/method/signature/iseq.rb +52 -0
- data/lib/decompiler/method/signature/node.rb +160 -0
- data/lib/decompiler/method/signature/signature.rb +23 -0
- data/lib/decompiler/module/as_code.rb +45 -0
- data/lib/decompiler/node/as_code.rb +233 -0
- data/lib/decompiler/node/as_expression.rb +619 -0
- data/lib/decompiler/proc/as_code.rb +23 -0
- data/lib/decompiler/proc/as_expression.rb +16 -0
- data/lib/decompiler/proc/signature.rb +184 -0
- data/lib/decompiler/vm/bytedecoder.rb +866 -0
- data/lib/decompiler/vm/iseq/as_code.rb +27 -0
- data/lib/decompiler/vm/iseq/as_expression.rb +26 -0
- data/test/test_as_code.rb +261 -0
- data/test/test_as_expression.rb +229 -0
- data/test/test_methodsig.rb +267 -0
- metadata +105 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'internal/node/as_code'
|
2
|
+
require 'internal/proc'
|
3
|
+
require 'internal/proc/signature'
|
4
|
+
|
5
|
+
class Proc
|
6
|
+
# Return a string representation of a proc's definition/body,
|
7
|
+
# similarly to +Method#as_code+.
|
8
|
+
def as_code(indent=0)
|
9
|
+
sig = self.signature
|
10
|
+
body_expression = self.body ? self.body.as_code(indent+1) : nil
|
11
|
+
s = "#{' '*indent}proc do"
|
12
|
+
if not sig.args.unspecified then
|
13
|
+
s += " #{sig}"
|
14
|
+
end
|
15
|
+
s += "\n"
|
16
|
+
if body_expression then
|
17
|
+
s += "#{body_expression}\n"
|
18
|
+
end
|
19
|
+
s += "#{' '*indent}end"
|
20
|
+
return s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'internal/node/as_expression'
|
2
|
+
require 'internal/proc/signature'
|
3
|
+
require 'internal/proc'
|
4
|
+
|
5
|
+
class Proc
|
6
|
+
# Return a single-line string representation of a proc's
|
7
|
+
# definition/body, similarly to +Method#as_expression+.
|
8
|
+
def as_expression
|
9
|
+
sig = self.signature
|
10
|
+
body_expression = self.body ? self.body.as_expression : nil
|
11
|
+
s = sig.args.unspecified ? "" : sig.to_s + ' '
|
12
|
+
b = body_expression ? body_expression + ' ' : ''
|
13
|
+
return "proc { #{s}#{b}}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'internal/node'
|
2
|
+
require 'internal/proc'
|
3
|
+
require 'internal/vm'
|
4
|
+
|
5
|
+
class Proc
|
6
|
+
class Arguments
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(names, multiple_assignment, rest_arg)
|
10
|
+
@names = names
|
11
|
+
@multiple_assignment = multiple_assignment
|
12
|
+
@rest_arg = rest_arg
|
13
|
+
end
|
14
|
+
|
15
|
+
def unspecified
|
16
|
+
if defined?(RubyVM) and defined?(RubyVM::InstructionSequence) then
|
17
|
+
# YARV
|
18
|
+
return @names.length == 0
|
19
|
+
else
|
20
|
+
# pre-YARV
|
21
|
+
@names.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def single_assignment
|
26
|
+
!@multiple_assignment
|
27
|
+
end
|
28
|
+
|
29
|
+
def multiple_assignment
|
30
|
+
@multiple_assignment
|
31
|
+
end
|
32
|
+
|
33
|
+
def names
|
34
|
+
@names
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](idx)
|
38
|
+
@names[idx]
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&block)
|
42
|
+
(@names || []).each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
@names.size
|
47
|
+
end
|
48
|
+
|
49
|
+
def rest_arg
|
50
|
+
@rest_arg
|
51
|
+
end
|
52
|
+
|
53
|
+
def empty_last_arg
|
54
|
+
if defined?(VM) and defined?(VM::InstructionSequence) then
|
55
|
+
# YARV
|
56
|
+
return (@rest_arg and @names[-1] == nil)
|
57
|
+
else
|
58
|
+
# pre-YARV
|
59
|
+
return (@names.size == 1 and not @rest_arg)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return an Arguments object representing the arguments in the order
|
65
|
+
# in which they appear in the argument list.
|
66
|
+
def arguments
|
67
|
+
has_rest_arg = self.has_rest_arg
|
68
|
+
|
69
|
+
if self.respond_to?(:var) then
|
70
|
+
# pre-YARV
|
71
|
+
case self.var
|
72
|
+
when Node::DASGN_CURR
|
73
|
+
return Arguments.new([ self.var.vid ], false, has_rest_arg ? 0 : nil)
|
74
|
+
when Node::MASGN
|
75
|
+
if self.var.head then
|
76
|
+
a = self.var.head.to_a
|
77
|
+
args = a.map { |n| n.vid }
|
78
|
+
else
|
79
|
+
args = []
|
80
|
+
end
|
81
|
+
if self.var.args then
|
82
|
+
args.push(self.var.args.vid)
|
83
|
+
end
|
84
|
+
return Arguments.new(args, true, has_rest_arg ? args.size - 1: nil)
|
85
|
+
when nil
|
86
|
+
return Arguments.new(nil, false, has_rest_arg ? 0 : nil)
|
87
|
+
when Fixnum
|
88
|
+
return Arguments.new([], false, has_rest_arg ? 0 : nil)
|
89
|
+
else
|
90
|
+
raise "Unexpected node type: #{self.var.class}"
|
91
|
+
end
|
92
|
+
elsif
|
93
|
+
# YARV
|
94
|
+
iseq = self.body
|
95
|
+
local_vars = iseq.local_table
|
96
|
+
has_rest_arg = iseq.arg_rest != -1
|
97
|
+
has_block_arg = iseq.arg_block != -1
|
98
|
+
num_args = \
|
99
|
+
iseq.argc + \
|
100
|
+
iseq.arg_opt_table.size + \
|
101
|
+
(has_rest_arg ? 1 : 0) + \
|
102
|
+
(has_block_arg ? 1 : 0)
|
103
|
+
names = local_vars[0...num_args]
|
104
|
+
# TODO: masgn
|
105
|
+
return Arguments.new(names, true, has_rest_arg ? -1 : nil)
|
106
|
+
else
|
107
|
+
return Arguments.new(nil, false, nil)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Return true if the proc has a rest arg
|
112
|
+
def has_rest_arg
|
113
|
+
if self.respond_to?(:var) then
|
114
|
+
# pre-YARV
|
115
|
+
has_rest_arg = false
|
116
|
+
if self.var then
|
117
|
+
if self.var.class == Node::MASGN then
|
118
|
+
if self.var.args then
|
119
|
+
has_rest_arg = true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
else
|
124
|
+
# YARV
|
125
|
+
rest = self.body.arg_rest
|
126
|
+
has_rest_arg = (rest >= 0 ? rest - 1 : nil)
|
127
|
+
end
|
128
|
+
return has_rest_arg
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return a hash mapping each argument name to a description of that
|
132
|
+
# argument.
|
133
|
+
def argument_info
|
134
|
+
args = self.arguments()
|
135
|
+
|
136
|
+
info = {}
|
137
|
+
args.each do |name|
|
138
|
+
info[name] = name.to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
# Rest arg
|
142
|
+
if args.rest_arg then
|
143
|
+
rest_name = args[args.rest_arg]
|
144
|
+
if rest_name then
|
145
|
+
info[rest_name] = "*#{rest_name}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
return info
|
150
|
+
end
|
151
|
+
|
152
|
+
class Signature
|
153
|
+
attr_reader :args, :arg_info
|
154
|
+
|
155
|
+
def initialize(args, arg_info)
|
156
|
+
@args = args
|
157
|
+
@arg_info = arg_info
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
if @args.unspecified then
|
162
|
+
return ""
|
163
|
+
elsif @args.multiple_assignment then
|
164
|
+
if @args.empty_last_arg then
|
165
|
+
params = @args.map{ |n| arg_info[n] }
|
166
|
+
return "|#{@arg_info[@args[0]]},|"
|
167
|
+
else
|
168
|
+
params = @args.map{ |n| arg_info[n] }
|
169
|
+
return "|#{params.join(', ')}|"
|
170
|
+
end
|
171
|
+
else
|
172
|
+
return "|#{@arg_info[@args[0]]}|"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Return a String representing the method's signature.
|
178
|
+
def signature
|
179
|
+
return Signature.new(
|
180
|
+
arguments(),
|
181
|
+
argument_info)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
@@ -0,0 +1,866 @@
|
|
1
|
+
require 'internal/vm/iseq'
|
2
|
+
require 'internal/vm/instruction'
|
3
|
+
require 'internal/vm/inline_cache'
|
4
|
+
require 'internal/vm/constants'
|
5
|
+
|
6
|
+
module Internal
|
7
|
+
|
8
|
+
# A module for decoding YARV bytecode.
|
9
|
+
#
|
10
|
+
# This is actually pretty cool. It's actually a miniature VM, where the
|
11
|
+
# result of evaluating an expression is itself another expression. This
|
12
|
+
# turns out to be much simpler than a full ruby VM, but I think one
|
13
|
+
# could use this as a base for building one.
|
14
|
+
#
|
15
|
+
# Example usage:
|
16
|
+
# env = Internal::ByteDecoder::Environment.new(is.local_table)
|
17
|
+
# is = RubyVM::InstructionSequence.new('1 + 1')
|
18
|
+
# is.bytedecode(env)
|
19
|
+
# env.expressions.each do |expr|
|
20
|
+
# puts expr
|
21
|
+
# end
|
22
|
+
# puts stack[-1]
|
23
|
+
#
|
24
|
+
module ByteDecoder
|
25
|
+
|
26
|
+
class Environment
|
27
|
+
attr_reader :stack
|
28
|
+
attr_reader :expressions
|
29
|
+
attr_reader :local_table
|
30
|
+
attr_accessor :last
|
31
|
+
attr_reader :seq
|
32
|
+
attr_accessor :pc
|
33
|
+
|
34
|
+
def initialize(local_table)
|
35
|
+
@stack = []
|
36
|
+
@expressions = []
|
37
|
+
@local_table = local_table
|
38
|
+
@last = nil
|
39
|
+
@pc = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def advance(instruction_length)
|
43
|
+
@pc += instruction_length
|
44
|
+
end
|
45
|
+
|
46
|
+
def remember(expression)
|
47
|
+
if not expression.is_a?(Expression::Literal) then
|
48
|
+
@expressions << expression
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Expression
|
54
|
+
attr_reader :pc
|
55
|
+
|
56
|
+
def initialize(pc)
|
57
|
+
@pc = pc
|
58
|
+
end
|
59
|
+
|
60
|
+
def <=>(rhs)
|
61
|
+
return @pc <=> rhs.pc
|
62
|
+
end
|
63
|
+
|
64
|
+
def fmt(arg)
|
65
|
+
if arg.respond_to?(:precedence)
|
66
|
+
p = arg.precedence
|
67
|
+
else
|
68
|
+
p = 0
|
69
|
+
end
|
70
|
+
|
71
|
+
if p >= self.precedence then
|
72
|
+
return "(#{arg})"
|
73
|
+
else
|
74
|
+
return arg
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Literal < Expression
|
79
|
+
attr_reader :value
|
80
|
+
|
81
|
+
def initialize(pc, value)
|
82
|
+
super(pc)
|
83
|
+
@value = value
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
case @value
|
88
|
+
when Regexp then "/#{@value.inspect[1..-2]}/"
|
89
|
+
else; return @value.inspect
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def precedence
|
94
|
+
return 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Infix < Expression
|
99
|
+
attr_reader :op
|
100
|
+
attr_reader :lhs
|
101
|
+
attr_reader :rhs
|
102
|
+
|
103
|
+
def initialize(pc, op, lhs, rhs)
|
104
|
+
super(pc)
|
105
|
+
@op = op
|
106
|
+
@lhs = lhs
|
107
|
+
@rhs = rhs
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
return "#{fmt(@lhs)} #{@op} #{fmt(@rhs)}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def precedence
|
115
|
+
case @op
|
116
|
+
when :*, :/, :%
|
117
|
+
return 2
|
118
|
+
when '+'.intern, '-'.intern
|
119
|
+
return 3
|
120
|
+
when :<<, :>>
|
121
|
+
return 4
|
122
|
+
when :>, :>=, :<, :<=, :==, :===, :!=
|
123
|
+
return 5
|
124
|
+
when :undef
|
125
|
+
return 6
|
126
|
+
else
|
127
|
+
raise ArgumentError, "Unknown op: #{@op}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Prefix < Expression
|
133
|
+
def initialize(pc, op, expr)
|
134
|
+
super(pc)
|
135
|
+
@op = op
|
136
|
+
@expr = expr
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_s
|
140
|
+
op = @op.to_s
|
141
|
+
op.chop! if op[-1] == ?@
|
142
|
+
if @op == '!'.intern and @expr.is_a?(Infix) and @expr.op == :== then
|
143
|
+
return "#{@expr.fmt(@expr.lhs)} != #{@expr.fmt(@expr.rhs)}"
|
144
|
+
elsif self.precedence < @expr.precedence then
|
145
|
+
return "#{op}(#{@expr})"
|
146
|
+
else
|
147
|
+
return "#{op}#{@expr}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def precedence
|
152
|
+
return 1
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Send < Expression
|
157
|
+
attr_reader :is_assignment
|
158
|
+
|
159
|
+
def initialize(pc, id, has_receiver, has_parens, receiver, block, splat_last, *args)
|
160
|
+
super(pc)
|
161
|
+
@id = id
|
162
|
+
@is_assignment = id.to_s[-1] == ?=
|
163
|
+
@has_receiver = has_receiver
|
164
|
+
@has_parens = has_parens
|
165
|
+
@receiver = receiver
|
166
|
+
@block = block
|
167
|
+
@splat_last = splat_last
|
168
|
+
@args = args
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
s = ''
|
173
|
+
receiver_str = @has_receiver \
|
174
|
+
? "#{@receiver}." \
|
175
|
+
: nil
|
176
|
+
args = @args.map { |x| x.to_s }
|
177
|
+
if @splat_last then
|
178
|
+
args[-1] = "*#{@args[-1]}"
|
179
|
+
end
|
180
|
+
if @is_assignment and args.size == 1 then
|
181
|
+
s = "#{receiver_str}#{@id.to_s[0..-2]} = #{args[0]}"
|
182
|
+
else
|
183
|
+
open = @has_parens ? '(' : ''
|
184
|
+
close = @has_parens ? ')' : ''
|
185
|
+
s = "#{receiver_str}#{@id}#{open}#{args.join(', ')}#{close}"
|
186
|
+
end
|
187
|
+
if @block then
|
188
|
+
# TODO: this code is duplicated elsewhere
|
189
|
+
# TODO: handle block args
|
190
|
+
env = Environment.new(@block.local_table)
|
191
|
+
@block.bytedecode(env)
|
192
|
+
expressions = env.expressions + env.stack
|
193
|
+
expressions.sort!
|
194
|
+
expressions.map! { |x| x.to_s }
|
195
|
+
if expressions.length == 1 and
|
196
|
+
expressions[0].is_a?(Literal) and
|
197
|
+
expressions[0].value == nil then
|
198
|
+
# empty
|
199
|
+
else
|
200
|
+
s << " { #{expressions.join('; ')} }"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
return s
|
204
|
+
end
|
205
|
+
|
206
|
+
def precedence
|
207
|
+
if @has_receiver then
|
208
|
+
if @receiver.respond_to?(:precedence) then
|
209
|
+
return @receiver.precedence
|
210
|
+
end
|
211
|
+
end
|
212
|
+
return 1
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
class Self < Expression
|
217
|
+
def initialize(pc)
|
218
|
+
super(pc)
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_s
|
222
|
+
return "self"
|
223
|
+
end
|
224
|
+
|
225
|
+
def precedence
|
226
|
+
return 1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class Hash < Expression
|
231
|
+
def initialize(pc, args)
|
232
|
+
super(pc)
|
233
|
+
@args = args
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_s
|
237
|
+
s = '{ '
|
238
|
+
a = []
|
239
|
+
i = 0
|
240
|
+
while i < @args.length do
|
241
|
+
a << "#{@args[i]} => #{@args[i + 1]}"
|
242
|
+
i += 2
|
243
|
+
end
|
244
|
+
s << a.join(', ')
|
245
|
+
s << ' ' if a.length != 0
|
246
|
+
s << '}'
|
247
|
+
return s
|
248
|
+
end
|
249
|
+
|
250
|
+
def precedence
|
251
|
+
return 1
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class Array < Expression
|
256
|
+
def initialize(pc, args)
|
257
|
+
super(pc)
|
258
|
+
@args = args
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_s
|
262
|
+
s = '[ '
|
263
|
+
s << @args.join(', ')
|
264
|
+
s << ' ]'
|
265
|
+
return s
|
266
|
+
end
|
267
|
+
|
268
|
+
def precedence
|
269
|
+
return 1
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class ConcatArray < Expression
|
274
|
+
def initialize(array, splat)
|
275
|
+
@array = array
|
276
|
+
@splat = splat
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_s
|
280
|
+
s = '[ '
|
281
|
+
case @array
|
282
|
+
when Array then s << @array.args.join(', ')
|
283
|
+
when Literal then s << @array.value.join(', ')
|
284
|
+
else; raise "Unexpected: #{@array.inspect}"
|
285
|
+
end
|
286
|
+
s << ', *'
|
287
|
+
s << @splat.to_s
|
288
|
+
s << ' ]'
|
289
|
+
end
|
290
|
+
|
291
|
+
def precedence
|
292
|
+
return 1
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
class Defined < Expression
|
297
|
+
def initialize(pc, arg)
|
298
|
+
super(pc)
|
299
|
+
@arg = arg
|
300
|
+
end
|
301
|
+
|
302
|
+
def to_s
|
303
|
+
return "defined?(#{@arg.to_s})"
|
304
|
+
end
|
305
|
+
|
306
|
+
def precedence
|
307
|
+
return 1
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class Variable < Expression
|
312
|
+
def initialize(pc, name)
|
313
|
+
super(pc)
|
314
|
+
@name = name
|
315
|
+
end
|
316
|
+
|
317
|
+
def to_s
|
318
|
+
return @name.to_s
|
319
|
+
end
|
320
|
+
|
321
|
+
def precedence
|
322
|
+
return 1
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
class Constant < Expression
|
327
|
+
def initialize(pc, klass, name)
|
328
|
+
super(pc)
|
329
|
+
@klass = klass
|
330
|
+
@name = name
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_s
|
334
|
+
if @klass then
|
335
|
+
if @klass == Object then
|
336
|
+
return "::#{@name}"
|
337
|
+
else
|
338
|
+
return "#{@klass}::#{@name}"
|
339
|
+
end
|
340
|
+
else
|
341
|
+
return "#{@name}"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def precedence
|
346
|
+
return 1
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class ConstantAssignment < Constant
|
351
|
+
def initialize(pc, klass, name, value)
|
352
|
+
super(pc, klass, name)
|
353
|
+
@value = value
|
354
|
+
end
|
355
|
+
|
356
|
+
def to_s
|
357
|
+
s = super()
|
358
|
+
s << " = #{@value}"
|
359
|
+
return s
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
class ConcatStrings < Expression
|
364
|
+
def initialize(pc, args)
|
365
|
+
super(pc)
|
366
|
+
@args = args
|
367
|
+
end
|
368
|
+
|
369
|
+
def to_s
|
370
|
+
s = "\""
|
371
|
+
@args.each do |arg|
|
372
|
+
case arg
|
373
|
+
when Literal
|
374
|
+
case arg.value
|
375
|
+
when String then s << arg.value
|
376
|
+
else; s << arg.to_s
|
377
|
+
end
|
378
|
+
else
|
379
|
+
s << "\#{#{arg.to_s}}"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
s << "\""
|
383
|
+
end
|
384
|
+
|
385
|
+
def precedence
|
386
|
+
return 1
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class Assignment < Expression
|
391
|
+
attr_reader :rhs
|
392
|
+
|
393
|
+
def initialize(pc, name, rhs)
|
394
|
+
super(pc)
|
395
|
+
@name = name
|
396
|
+
@rhs = rhs
|
397
|
+
end
|
398
|
+
|
399
|
+
def to_s
|
400
|
+
return "#{@name} = #{@rhs}"
|
401
|
+
end
|
402
|
+
|
403
|
+
def precedence
|
404
|
+
return 5
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
class ToRegexp < Expression
|
409
|
+
def initialize(pc, value)
|
410
|
+
super(pc)
|
411
|
+
@value = value
|
412
|
+
end
|
413
|
+
|
414
|
+
def to_s
|
415
|
+
case @value
|
416
|
+
when ConcatStrings
|
417
|
+
string = @value.to_s
|
418
|
+
unstring = string[1..-2]
|
419
|
+
return Regexp.compile(unstring).inspect
|
420
|
+
else
|
421
|
+
return Regexp.compile(@value.to_s).inspect
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def precedence
|
426
|
+
return 1
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
class Throw < Expression
|
431
|
+
def initialize(pc, value)
|
432
|
+
super(pc)
|
433
|
+
@value = value
|
434
|
+
end
|
435
|
+
|
436
|
+
def to_s
|
437
|
+
# TODO: not all throws are breaks...
|
438
|
+
if not @value or (@value.is_a?(Literal) and @value.value == nil) then
|
439
|
+
return "break"
|
440
|
+
else
|
441
|
+
return "break #{@value}"
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def precedence
|
446
|
+
return 1
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
end # ByteDecoder
|
452
|
+
|
453
|
+
end # Internal
|
454
|
+
|
455
|
+
class RubyVM
|
456
|
+
class Instruction
|
457
|
+
include Internal::ByteDecoder
|
458
|
+
|
459
|
+
class PUTOBJECT
|
460
|
+
def bytedecode(env)
|
461
|
+
env.stack.push Expression::Literal.new(env.pc, self.operands[0])
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
INFIX_OPCODES = {
|
466
|
+
OPT_PLUS => '+'.intern,
|
467
|
+
OPT_MINUS => '-'.intern,
|
468
|
+
OPT_MULT => :*,
|
469
|
+
OPT_DIV => :/,
|
470
|
+
OPT_MOD => :%,
|
471
|
+
OPT_LTLT => :<<,
|
472
|
+
# OPT_GTGT => :>>,
|
473
|
+
OPT_EQ => :==,
|
474
|
+
OPT_NEQ => :!=,
|
475
|
+
OPT_GT => :>,
|
476
|
+
OPT_GE => :>=,
|
477
|
+
OPT_LT => :<,
|
478
|
+
OPT_LE => :<=,
|
479
|
+
}
|
480
|
+
|
481
|
+
INFIX_OPERATORS = INFIX_OPCODES.values + [ :===, :>> ]
|
482
|
+
|
483
|
+
INFIX_OPCODES.each do |klass, op|
|
484
|
+
klass.class_eval do
|
485
|
+
define_method(:bytedecode) do |env|
|
486
|
+
rhs = env.stack.pop
|
487
|
+
lhs = env.stack.pop
|
488
|
+
env.stack.push Expression::Infix.new(env.pc, op, lhs, rhs)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
PREFIX_OPCODES = {
|
494
|
+
OPT_NOT => :!,
|
495
|
+
}
|
496
|
+
|
497
|
+
if defined?(UNDEF)
|
498
|
+
PREFIX_OPCODES[UNDEF] = :undef
|
499
|
+
end
|
500
|
+
|
501
|
+
PREFIX_OPERATORS = PREFIX_OPCODES.values + [ :~, :+@, :-@ ]
|
502
|
+
|
503
|
+
PREFIX_OPCODES.each do |klass, op|
|
504
|
+
klass.class_eval do
|
505
|
+
define_method(:bytedecode) do |env|
|
506
|
+
expr = env.stack.pop
|
507
|
+
env.stack.push Expression::Prefix.new(env.pc, op, expr)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
class TRACE
|
513
|
+
def bytedecode(env)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
class LEAVE
|
518
|
+
def bytedecode(env)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
LITERAL_OPCODES = [
|
523
|
+
PUTNIL,
|
524
|
+
DUPARRAY,
|
525
|
+
PUTSTRING,
|
526
|
+
]
|
527
|
+
|
528
|
+
LITERAL_OPCODES.each do |klass|
|
529
|
+
klass.class_eval do
|
530
|
+
define_method(:bytedecode) do |env|
|
531
|
+
env.stack.push Expression::Literal.new(env.pc, @operands[0])
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
class SEND
|
537
|
+
def bytedecode(env)
|
538
|
+
id = @operands[0]
|
539
|
+
num_args = @operands[1]
|
540
|
+
args = []
|
541
|
+
num_args.times do
|
542
|
+
args.unshift env.stack.pop
|
543
|
+
end
|
544
|
+
has_receiver = !flag_set(RubyVM::CALL_FCALL_BIT)
|
545
|
+
has_parens = !flag_set(RubyVM::CALL_VCALL_BIT)
|
546
|
+
splat_last = flag_set(RubyVM::CALL_ARGS_SPLAT_BIT)
|
547
|
+
receiver = env.stack.pop
|
548
|
+
block = @operands[2]
|
549
|
+
if INFIX_OPERATORS.include?(id) and args.size == 1 then
|
550
|
+
env.stack.push Expression::Infix.new(env.pc, id, receiver, args[0])
|
551
|
+
elsif PREFIX_OPERATORS.include?(id) and args.size == 0 then
|
552
|
+
env.stack.push Expression::Prefix.new(env.pc, id, receiver)
|
553
|
+
else
|
554
|
+
env.stack.push Expression::Send.new(
|
555
|
+
env.pc, id, has_receiver, has_parens, receiver, block, splat_last, *args)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def flag_set(flag)
|
560
|
+
flags = @operands[3]
|
561
|
+
return flags & flag == flag
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
class PUTSELF
|
566
|
+
def bytedecode(env)
|
567
|
+
env.stack.push Expression::Self.new(env.pc)
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
class NEWHASH
|
572
|
+
def bytedecode(env)
|
573
|
+
i = @operands[0]
|
574
|
+
args = []
|
575
|
+
while i > 0 do
|
576
|
+
args.unshift env.stack.pop
|
577
|
+
i -= 1
|
578
|
+
end
|
579
|
+
env.stack.push Expression::Hash.new(env.pc, args)
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
class NEWARRAY
|
584
|
+
def bytedecode(env)
|
585
|
+
i = @operands[0]
|
586
|
+
args = []
|
587
|
+
while i > 0 do
|
588
|
+
args.unshift env.stack.pop
|
589
|
+
i -= 1
|
590
|
+
end
|
591
|
+
env.stack.push Expression::Array.new(env.pc, args)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
class DEFINED
|
596
|
+
def bytedecode(env)
|
597
|
+
env.stack.push Expression::Defined.new(env.pc, @operands[1])
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
GET_VARIABLE_OPCODES = [
|
602
|
+
GETCLASSVARIABLE,
|
603
|
+
GETINSTANCEVARIABLE,
|
604
|
+
GETGLOBAL,
|
605
|
+
]
|
606
|
+
|
607
|
+
GET_VARIABLE_OPCODES.each do |klass|
|
608
|
+
klass.class_eval do
|
609
|
+
define_method(:bytedecode) do |env|
|
610
|
+
env.stack.push Expression::Variable.new(env.pc, @operands[0])
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
SET_VARIABLE_OPCODES = [
|
616
|
+
SETCLASSVARIABLE,
|
617
|
+
SETINSTANCEVARIABLE,
|
618
|
+
SETGLOBAL,
|
619
|
+
]
|
620
|
+
|
621
|
+
SET_VARIABLE_OPCODES.each do |klass|
|
622
|
+
klass.class_eval do
|
623
|
+
define_method(:bytedecode) do |env|
|
624
|
+
value = env.stack.pop
|
625
|
+
env.stack.delete_at(-1) # TODO: dup'd value.. is this right?
|
626
|
+
env.stack.push Expression::Assignment.new(env.pc, @operands[0], value)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
class GETCONSTANT
|
632
|
+
def bytedecode(env)
|
633
|
+
klass = env.stack.pop
|
634
|
+
env.stack.push Expression::Constant.new(env.pc, klass, @operands[0])
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
class SETCONSTANT
|
639
|
+
def bytedecode(env)
|
640
|
+
klass = env.stack.pop
|
641
|
+
value = env.stack.pop
|
642
|
+
env.stack.push Expression::ConstantAssignment.new(env.pc, klass, @operands[0], value)
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
class GETSPECIAL
|
647
|
+
def bytedecode(env)
|
648
|
+
type = @operands[1] >> 1
|
649
|
+
type += ?0.ord if type < 10
|
650
|
+
env.stack.push Expression::Variable.new(env.pc, "$#{type.chr}")
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
class GETINLINECACHE
|
655
|
+
def bytedecode(env)
|
656
|
+
env.stack.push nil
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
class SETINLINECACHE
|
661
|
+
def bytedecode(env)
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
class NOP
|
666
|
+
def bytedecode(env)
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
class TOSTRING
|
671
|
+
def bytedecode(env)
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
class CONCATSTRINGS
|
676
|
+
def bytedecode(env)
|
677
|
+
i = @operands[0]
|
678
|
+
args = []
|
679
|
+
while i > 0 do
|
680
|
+
args.unshift env.stack.pop
|
681
|
+
i -= 1
|
682
|
+
end
|
683
|
+
env.stack.push Expression::ConcatStrings.new(env.pc, args)
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
class TOREGEXP
|
688
|
+
def bytedecode(env)
|
689
|
+
env.stack.push Expression::ToRegexp.new(env.pc, env.stack.pop)
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
class DUP
|
694
|
+
def bytedecode(env)
|
695
|
+
arg = env.stack.pop
|
696
|
+
env.stack.push arg
|
697
|
+
env.stack.push arg
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
class SETLOCAL
|
702
|
+
def bytedecode(env)
|
703
|
+
idx = env.local_table.size - @operands[0] + 1
|
704
|
+
name = env.local_table[idx]
|
705
|
+
value = env.stack.pop
|
706
|
+
env.stack.push Expression::Assignment.new(env.pc, name, value)
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
class GETLOCAL
|
711
|
+
def bytedecode(env)
|
712
|
+
idx = env.local_table.size - @operands[0] + 1
|
713
|
+
name = env.local_table[idx]
|
714
|
+
env.stack.push Expression::Variable.new(env.pc, name)
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
class SETDYNAMIC
|
719
|
+
def bytedecode(env)
|
720
|
+
idx = env.local_table.size - @operands[0]
|
721
|
+
name = env.local_table[idx]
|
722
|
+
value = env.stack.pop
|
723
|
+
env.stack.push Expression::Assignment.new(env.pc, name, value)
|
724
|
+
end
|
725
|
+
end
|
726
|
+
|
727
|
+
class GETDYNAMIC
|
728
|
+
def bytedecode(env)
|
729
|
+
idx = env.local_table.size - @operands[0]
|
730
|
+
name = env.local_table[idx]
|
731
|
+
env.stack.push Expression::Variable.new(env.pc, name)
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
class SETN
|
736
|
+
# set nth stack entry to stack top
|
737
|
+
def bytedecode(env)
|
738
|
+
n = @operands[0]
|
739
|
+
dest = -(n+1)
|
740
|
+
if env.stack[dest].is_a?(Expression) then
|
741
|
+
env.remember env.stack[dest]
|
742
|
+
end
|
743
|
+
env.stack[dest] = env.stack[-1]
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
class TOPN
|
748
|
+
# get nth stack entry from stack top
|
749
|
+
def bytedecode(env)
|
750
|
+
n = @operands[0]
|
751
|
+
idx = -(n+1)
|
752
|
+
env.stack[idx]
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
class POP
|
757
|
+
def bytedecode(env)
|
758
|
+
top = env.stack[-1]
|
759
|
+
if top.is_a?(Expression::Send) and top.is_assignment then
|
760
|
+
# special case - the return value from the assignment gets
|
761
|
+
# thrown away and the result is the rhs
|
762
|
+
env.remember env.stack[-2]
|
763
|
+
env.stack.delete_at(-2)
|
764
|
+
end
|
765
|
+
env.remember top
|
766
|
+
env.stack.pop
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
class THROW
|
771
|
+
def bytedecode(env)
|
772
|
+
value = env.stack.pop
|
773
|
+
env.remember env.stack.pop
|
774
|
+
env.stack.push Expression::Throw.new(env.pc, value)
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
class CONCATARRAY
|
779
|
+
def bytedecode(env)
|
780
|
+
splat = env.stack.pop
|
781
|
+
array = env.stack.pop
|
782
|
+
env.stack.push Expression::ConcatArray.new(array, splat)
|
783
|
+
end
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
class InstructionSequence
|
788
|
+
def bytedecode(env, start_pc=0, end_pc=nil, &block)
|
789
|
+
self.each(start_pc) do |instruction|
|
790
|
+
# p instruction
|
791
|
+
instruction.bytedecode(env)
|
792
|
+
# p env.stack
|
793
|
+
env.advance(instruction.length)
|
794
|
+
break if end_pc and env.pc >= end_pc
|
795
|
+
break if block and block.call(instruction)
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
def opt_pc
|
800
|
+
opt_table = self.arg_opt_table
|
801
|
+
return opt_table.length > 0 ? opt_table[-1] : 0
|
802
|
+
end
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
if __FILE__ == $0 then
|
807
|
+
# def foo; @@foo = 1; end
|
808
|
+
# def foo; a = [2, 3]; foo(1, *a); end
|
809
|
+
# def foo; not true; end
|
810
|
+
# def foo; catch(:foo) { throw :foo; 42 }; end
|
811
|
+
# def foo; a ? b : c; end
|
812
|
+
# def foo; loop { a = 1; break }; end
|
813
|
+
# def foo; ::BAR; end
|
814
|
+
# def foo; a != b; end
|
815
|
+
# def foo; 1 - 2; end
|
816
|
+
# def foo; +a; end
|
817
|
+
# def foo; !a; end
|
818
|
+
# def foo; a << b; end
|
819
|
+
# def foo; a === b; end
|
820
|
+
# def foo; []; end
|
821
|
+
def foo; foo.bar = 42; end
|
822
|
+
# def foo; h = {}; h.default = true; h; end
|
823
|
+
# def foo; @FOO; end
|
824
|
+
# def foo; $FOO; end
|
825
|
+
# def foo; /foo#{bar}/; end
|
826
|
+
# def foo; /foo/; end
|
827
|
+
# def foo; foo = 1; bar=2; baz=3 end
|
828
|
+
# def foo; "foo#{bar}"; end
|
829
|
+
# def foo; $`; end
|
830
|
+
# def foo; FOO; end
|
831
|
+
# def foo; @@foo; end
|
832
|
+
# def foo; defined?(FOO); end
|
833
|
+
# def foo; [1+1, 2, 3, 4] ; end
|
834
|
+
# def foo; { 1 => 2, 3 => 4 }; end
|
835
|
+
# def foo; foo(1 + 2 * (3 + 4), 5); end
|
836
|
+
# def foo; 1 + 2 * (3 + 4); end
|
837
|
+
|
838
|
+
n = method(:foo).body
|
839
|
+
is = n.body
|
840
|
+
puts is.disasm
|
841
|
+
|
842
|
+
env = Internal::ByteDecoder::Environment.new(is.local_table)
|
843
|
+
s = ''
|
844
|
+
# puts "local_table = #{is.local_table.inspect}"
|
845
|
+
is.each do |i|
|
846
|
+
# p i.operand_types, i.operand_names
|
847
|
+
# p i #, i.operand_types, i.operand_names
|
848
|
+
print i.class, ' '
|
849
|
+
a = []
|
850
|
+
i.operand_names.each_with_index do |name, idx|
|
851
|
+
a << "#{name}(#{i.operand_types[idx]})=#{i.operands[idx].inspect}"
|
852
|
+
end
|
853
|
+
puts a.join(', ')
|
854
|
+
i.bytedecode(env)
|
855
|
+
env.advance(i.length)
|
856
|
+
# p env.stack
|
857
|
+
# p env.stack.map { |x| x.to_s }
|
858
|
+
# puts
|
859
|
+
end
|
860
|
+
|
861
|
+
puts "---"
|
862
|
+
(env.expressions + env.stack).sort.each do |expr|
|
863
|
+
puts expr.to_s
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|