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
data/README.rdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Ruby-decompiler is a decompiler for ruby code. With it you can...
|
2
|
+
|
3
|
+
View method signatures:
|
4
|
+
|
5
|
+
irb(main):015:0> def foo(a, b, *rest, &block); end; method(:foo).signature
|
6
|
+
=> #<MethodSig::Signature:0x4037093c @origin_class=Object, @arg_info={:b=>"b",
|
7
|
+
:block=>"&block", :a=>"a", :rest=>"*rest"}, @name="foo", @arg_names=[:a,
|
8
|
+
:b, :rest, :block]>
|
9
|
+
irb(main):016:0> proc { |x, y, *rest| }.signature
|
10
|
+
=> #<Proc::Signature:0x4036cf30 @args=#<Proc::Arguments:0x4036d020 @rest_arg=2,
|
11
|
+
@multiple_assignment=true, @names=[:x, :y, :rest]>, @arg_info={:x=>"x", :y=>"y",
|
12
|
+
:rest=>"*rest"}>
|
13
|
+
|
14
|
+
And reconstruct compiled methods:
|
15
|
+
|
16
|
+
irb(main):001:0> def foo(a, b, *rest, &block)
|
17
|
+
irb(main):002:1> begin
|
18
|
+
irb(main):003:2* if not a and not b then
|
19
|
+
irb(main):004:3* raise "Need more input!"
|
20
|
+
irb(main):005:3> end
|
21
|
+
irb(main):006:2> return a + b
|
22
|
+
irb(main):007:2> ensure
|
23
|
+
irb(main):008:2* puts "In ensure block"
|
24
|
+
irb(main):009:2> end
|
25
|
+
irb(main):010:1> end
|
26
|
+
=> nil
|
27
|
+
irb(main):011:0> m = method(:foo)
|
28
|
+
=> #<Method: Object#foo>
|
29
|
+
irb(main):012:0> puts m.as_code
|
30
|
+
def foo(a, b, *rest, &block)
|
31
|
+
begin
|
32
|
+
(raise("Need more input!")) if (not a and not b)
|
33
|
+
return a + b
|
34
|
+
ensure
|
35
|
+
puts("In ensure block")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
=> nil
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'internal/node/as_code'
|
2
|
+
require 'internal/vm/iseq/as_code'
|
3
|
+
require 'internal/method'
|
4
|
+
require 'internal/method/signature'
|
5
|
+
|
6
|
+
module MethodAsCode
|
7
|
+
# Returns a string representation of the method definition/body.
|
8
|
+
#
|
9
|
+
# irb(main):001:0> def foo(a, b, *rest, &block)
|
10
|
+
# irb(main):002:1> begin
|
11
|
+
# irb(main):003:2* if not a and not b then
|
12
|
+
# irb(main):004:3* raise "Need more input!"
|
13
|
+
# irb(main):005:3> end
|
14
|
+
# irb(main):006:2> return a + b
|
15
|
+
# irb(main):007:2> ensure
|
16
|
+
# irb(main):008:2* puts "In ensure block"
|
17
|
+
# irb(main):009:2> end
|
18
|
+
# irb(main):010:1> end
|
19
|
+
# => nil
|
20
|
+
# irb(main):011:0> m = method(:foo)
|
21
|
+
# => #<Method: Object#foo>
|
22
|
+
# irb(main):012:0> puts m.as_code
|
23
|
+
# def foo(a, b, *rest, &block)
|
24
|
+
# begin
|
25
|
+
# (raise("Need more input!")) if (not a and not b)
|
26
|
+
# return a + b
|
27
|
+
# ensure
|
28
|
+
# puts("In ensure block")
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
def as_code(indent=0, name=nil)
|
33
|
+
sig = self.signature
|
34
|
+
if self.body.respond_to?(:body) then
|
35
|
+
# YARV
|
36
|
+
body_expression = self.body.body.as_code(indent+1)
|
37
|
+
else
|
38
|
+
# pre-YARV
|
39
|
+
body_expression = self.body ? self.body.as_code(indent+1) : ''
|
40
|
+
end
|
41
|
+
name ||= sig.name
|
42
|
+
s = "#{' '*indent}def #{name}(#{sig.param_list})\n"
|
43
|
+
if body_expression then
|
44
|
+
s += "#{body_expression}\n"
|
45
|
+
end
|
46
|
+
s += "#{' '*indent}end"
|
47
|
+
return s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Method
|
52
|
+
include MethodAsCode
|
53
|
+
end
|
54
|
+
|
55
|
+
class UnboundMethod
|
56
|
+
include MethodAsCode
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'internal/node/as_expression'
|
2
|
+
require 'internal/vm/iseq/as_expression'
|
3
|
+
require 'internal/method/signature'
|
4
|
+
require 'internal/method'
|
5
|
+
|
6
|
+
module MethodAsExpression
|
7
|
+
# Return a single-line string representation of a method
|
8
|
+
# TODO: this method would be more aptly named "as_expression_string".
|
9
|
+
def as_expression
|
10
|
+
sig = self.signature
|
11
|
+
if self.body.respond_to?(:body) then
|
12
|
+
# YARV
|
13
|
+
body_expression = self.body.body.as_expression
|
14
|
+
else
|
15
|
+
# pre-YARV
|
16
|
+
body_expression = self.body.as_expression
|
17
|
+
end
|
18
|
+
if body_expression then
|
19
|
+
return "def #{sig.name}(#{sig.param_list}); #{body_expression}; end"
|
20
|
+
else
|
21
|
+
return "def #{sig.name}(#{sig.param_list}); end"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Method
|
27
|
+
include MethodAsExpression
|
28
|
+
end
|
29
|
+
|
30
|
+
class UnboundMethod
|
31
|
+
include MethodAsExpression
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MethodOrigin
|
2
|
+
# An abstraction for a method origin.
|
3
|
+
class Origin
|
4
|
+
attr_reader :file, :line
|
5
|
+
|
6
|
+
def initialize(file, line)
|
7
|
+
@file = file
|
8
|
+
@line = line
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
return "#{file}:#{line}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return a Method::Origin representing where the method was defined.
|
17
|
+
def origin
|
18
|
+
block = body().next
|
19
|
+
return Origin.new(block.nd_file, block.nd_line)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Method
|
24
|
+
include MethodOrigin
|
25
|
+
end
|
26
|
+
|
27
|
+
class UnboundMethod
|
28
|
+
include MethodOrigin
|
29
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'internal/method'
|
2
|
+
require 'internal/node'
|
3
|
+
require 'internal/node/as_expression'
|
4
|
+
require 'internal/method/signature/argument'
|
5
|
+
require 'internal/method/signature/node'
|
6
|
+
require 'internal/method/signature/signature'
|
7
|
+
|
8
|
+
if defined?(RubyVM) then
|
9
|
+
require 'internal/vm/bytedecoder'
|
10
|
+
require 'internal/method/signature/iseq'
|
11
|
+
end
|
12
|
+
|
13
|
+
module MethodSig
|
14
|
+
# Return the names of the local variables of this method.
|
15
|
+
def local_vars
|
16
|
+
return self.body.local_vars
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return the names of the arguments this method takes, in the order in
|
20
|
+
# which they appear in the argument list.
|
21
|
+
def argument_names
|
22
|
+
return self.body.argument_names
|
23
|
+
end
|
24
|
+
|
25
|
+
def args_node
|
26
|
+
return self.body.args_node
|
27
|
+
end
|
28
|
+
private :args_node
|
29
|
+
|
30
|
+
# If this method has a "rest" argument, that is, it has an argument
|
31
|
+
# that is preceded by an asterisk (*) in the argument list, then
|
32
|
+
# return its index, otherwise return nil.
|
33
|
+
def rest_arg
|
34
|
+
return self.body.rest_arg
|
35
|
+
end
|
36
|
+
|
37
|
+
# If this method has a "block" argument, that is, it has an argument
|
38
|
+
# that is preceded by an ampersand (&) in the argument list, then
|
39
|
+
# return its index, otherwise return nil.
|
40
|
+
def block_arg
|
41
|
+
return self.body.block_arg
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_optional_args(args, args_node, names)
|
45
|
+
self.body.set_optional_args(args, args_node, names)
|
46
|
+
end
|
47
|
+
private :set_optional_args
|
48
|
+
|
49
|
+
# Return a hash mapping each argument name to a description of that
|
50
|
+
# argument.
|
51
|
+
def arguments
|
52
|
+
names = self.argument_names()
|
53
|
+
block_arg = self.block_arg()
|
54
|
+
|
55
|
+
args = {}
|
56
|
+
names.each do |name|
|
57
|
+
args[name] = Argument.new(name, false, false)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Optional args
|
61
|
+
args_node = args_node()
|
62
|
+
set_optional_args(args, args_node, names)
|
63
|
+
|
64
|
+
# Rest arg
|
65
|
+
if self.rest_arg then
|
66
|
+
rest_name = names[rest_arg]
|
67
|
+
args[rest_name] = Argument.new(rest_name, true, false)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Block arg
|
71
|
+
if block_arg then
|
72
|
+
block_name = names[block_arg]
|
73
|
+
args[block_name] = Argument.new(block_name, false, true)
|
74
|
+
end
|
75
|
+
|
76
|
+
return args
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Method
|
81
|
+
include MethodSig
|
82
|
+
|
83
|
+
# Return a String representing the method's signature.
|
84
|
+
def signature
|
85
|
+
return Signature.new(
|
86
|
+
attached_class(),
|
87
|
+
method_oid().to_s,
|
88
|
+
argument_names(),
|
89
|
+
arguments())
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class UnboundMethod
|
94
|
+
include MethodSig
|
95
|
+
|
96
|
+
# Return a String representing the method's signature.
|
97
|
+
def signature
|
98
|
+
return Signature.new(
|
99
|
+
origin_class(),
|
100
|
+
method_oid().to_s,
|
101
|
+
argument_names(),
|
102
|
+
arguments())
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if __FILE__ == $0 then
|
107
|
+
def foo(); end
|
108
|
+
puts method(:foo).signature
|
109
|
+
|
110
|
+
def foo(foo); end
|
111
|
+
puts method(:foo).signature
|
112
|
+
|
113
|
+
def foo(foo, bar); end
|
114
|
+
puts method(:foo).signature
|
115
|
+
|
116
|
+
def foo(foo=42, bar=10); end
|
117
|
+
puts method(:foo).signature
|
118
|
+
|
119
|
+
def foo(*args); end
|
120
|
+
puts method(:foo).signature
|
121
|
+
|
122
|
+
def foo(foo, bar=42, *args, &block); end
|
123
|
+
puts method(:foo).signature
|
124
|
+
puts method(:foo).origin
|
125
|
+
|
126
|
+
def foo(foo, bar=obj.foo(1, 2, foo(10)), *args, &block); end
|
127
|
+
puts method(:foo).signature
|
128
|
+
|
129
|
+
def foo(foo, bar=obj.foo(1 + 1), *args, &block); end
|
130
|
+
puts method(:foo).signature
|
131
|
+
|
132
|
+
def foo(foo, bar=true ? false : 0, *args, &block); end
|
133
|
+
puts method(:foo).signature
|
134
|
+
|
135
|
+
def foo(foo, bar=true, *args, &block); end
|
136
|
+
puts method(:foo).signature
|
137
|
+
|
138
|
+
def foo(foo, bar=nil, *args, &block); end
|
139
|
+
puts method(:foo).signature
|
140
|
+
|
141
|
+
def foo(foo, bar={1=>2}, *args, &block); end
|
142
|
+
puts method(:foo).signature
|
143
|
+
|
144
|
+
def foo(foo, bar=[1,2], *args, &block); end
|
145
|
+
puts method(:foo).signature
|
146
|
+
end
|
147
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
module MethodSig
|
3
|
+
class Argument
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def required?
|
7
|
+
return !optional?
|
8
|
+
end
|
9
|
+
|
10
|
+
def optional?
|
11
|
+
return rest? || block?
|
12
|
+
end
|
13
|
+
|
14
|
+
def rest?
|
15
|
+
return @is_rest
|
16
|
+
end
|
17
|
+
|
18
|
+
def block?
|
19
|
+
return @is_block
|
20
|
+
end
|
21
|
+
|
22
|
+
def default
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(name, is_rest, is_block)
|
27
|
+
@name = name
|
28
|
+
@is_rest = is_rest
|
29
|
+
@is_block = is_block
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
if @is_rest then
|
34
|
+
prefix = "*"
|
35
|
+
elsif @is_block then
|
36
|
+
prefix = "&"
|
37
|
+
end
|
38
|
+
|
39
|
+
if self.default then
|
40
|
+
suffix = "=#{default()}"
|
41
|
+
end
|
42
|
+
|
43
|
+
return "#{prefix}#{@name}#{suffix}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class OptionalArgument < Argument
|
48
|
+
def optional?
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class NodeOptionalArgument < OptionalArgument
|
54
|
+
attr_reader :node_for_default
|
55
|
+
attr_reader :default
|
56
|
+
|
57
|
+
def initialize(name, default, node_for_default, is_rest, is_block)
|
58
|
+
super(name, is_rest, is_block)
|
59
|
+
@default = default
|
60
|
+
@node_for_default = node_for_default
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class YarvOptionalArgument < OptionalArgument
|
65
|
+
attr_reader :iseq
|
66
|
+
attr_reader :pc_start
|
67
|
+
attr_reader :local_idx
|
68
|
+
|
69
|
+
def initialize(name, iseq, pc_start, local_idx, is_rest, is_block)
|
70
|
+
super(name, is_rest, is_block)
|
71
|
+
@iseq = iseq
|
72
|
+
@pc_start = pc_start
|
73
|
+
@local_idx = local_idx
|
74
|
+
@default = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def inspect
|
78
|
+
default()
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def default
|
83
|
+
return @default if @default
|
84
|
+
|
85
|
+
env = Internal::ByteDecoder::Environment.new(@iseq.local_table())
|
86
|
+
local_table_idx = local_table_idx()
|
87
|
+
@iseq.bytedecode(env, @pc_start) do |instr|
|
88
|
+
RubyVM::Instruction::SETLOCAL === instr &&
|
89
|
+
instr.operands[0] == local_table_idx
|
90
|
+
end
|
91
|
+
expressions = env.expressions + env.stack
|
92
|
+
|
93
|
+
@default = expressions[0].rhs.to_s
|
94
|
+
return @default
|
95
|
+
end
|
96
|
+
|
97
|
+
def local_table_idx
|
98
|
+
return @iseq.local_table.size - @local_idx + 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class RubyVM
|
2
|
+
# YARV 1.9.2 and later
|
3
|
+
class InstructionSequence
|
4
|
+
include MethodSig
|
5
|
+
|
6
|
+
def local_vars
|
7
|
+
local_vars = self.local_table
|
8
|
+
return local_vars
|
9
|
+
end
|
10
|
+
|
11
|
+
def argument_names
|
12
|
+
local_vars = self.local_vars
|
13
|
+
opt_args = self.arg_opt_table
|
14
|
+
opt_args.pop # last arg is a pointer to the start of the code
|
15
|
+
num_args = \
|
16
|
+
self.argc + \
|
17
|
+
opt_args.size + \
|
18
|
+
(rest_arg ? 1 : 0) + \
|
19
|
+
(block_arg ? 1 : 0)
|
20
|
+
return local_vars[0...num_args]
|
21
|
+
end
|
22
|
+
|
23
|
+
def args_node
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def rest_arg
|
28
|
+
arg_rest = self.arg_rest
|
29
|
+
return arg_rest >= 0 ? arg_rest : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def block_arg
|
33
|
+
arg_block = self.arg_block
|
34
|
+
return arg_block >= 0 ? arg_block : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_optional_args(args, args_node, names)
|
38
|
+
opt_table = self.arg_opt_table
|
39
|
+
opt_table.pop
|
40
|
+
first_opt_idx =
|
41
|
+
names.size -
|
42
|
+
opt_table.size -
|
43
|
+
(self.rest_arg ? 1 : 0) -
|
44
|
+
(self.block_arg ? 1 : 0)
|
45
|
+
opt_table.each_with_index do |pc, idx|
|
46
|
+
name = names[first_opt_idx + idx]
|
47
|
+
args[name] = YarvOptionalArgument.new(name, self, pc, idx, false, false)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|