jvm_bytecode 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/.editorconfig +4 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +90 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/jvm_bytecode.gemspec +29 -0
- data/lib/jvm_bytecode.rb +27 -0
- data/lib/jvm_bytecode/access_flag.rb +20 -0
- data/lib/jvm_bytecode/attributes/attribute.rb +50 -0
- data/lib/jvm_bytecode/attributes/code.rb +60 -0
- data/lib/jvm_bytecode/attributes/source_file.rb +24 -0
- data/lib/jvm_bytecode/class_file.rb +117 -0
- data/lib/jvm_bytecode/constant_pool.rb +84 -0
- data/lib/jvm_bytecode/constants/class.rb +25 -0
- data/lib/jvm_bytecode/constants/constant.rb +28 -0
- data/lib/jvm_bytecode/constants/method_ref.rb +7 -0
- data/lib/jvm_bytecode/constants/name_and_type.rb +30 -0
- data/lib/jvm_bytecode/constants/ref.rb +28 -0
- data/lib/jvm_bytecode/constants/utf8.rb +30 -0
- data/lib/jvm_bytecode/errors/attribute_error.rb +6 -0
- data/lib/jvm_bytecode/errors/constant_error.rb +6 -0
- data/lib/jvm_bytecode/errors/decode_error.rb +6 -0
- data/lib/jvm_bytecode/errors/opcode_error.rb +6 -0
- data/lib/jvm_bytecode/extensions.rb +15 -0
- data/lib/jvm_bytecode/field.rb +55 -0
- data/lib/jvm_bytecode/instructions/instruction.rb +55 -0
- data/lib/jvm_bytecode/instructions/instruction_set.rb +48 -0
- data/lib/jvm_bytecode/method.rb +69 -0
- data/lib/jvm_bytecode/version.rb +3 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a1b9757f79bb089650a53b65eda732b46460e3e
|
4
|
+
data.tar.gz: 4a4ebd63056edd36bcb5721621df9aac52a3c4ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 133fc00f84dc509ad1acc5573dc5a897a592284ba152f4ccc8135c023bc6feabcabbab318d080611ee647c195fd1c064faf566ff74d51d2880900c2124210aa2
|
7
|
+
data.tar.gz: 9cfcfd3eb75bce7be9951b71af7c5fccafd05a7df6bbc5b0fcbfb5535a780c8a16fe3ecec83f1cb226139aa1b64b637983552797c388822cadbeb3aeca54cc31
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Takanori.Murakami
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
Tool for generating and decoding JVM bytecode.
|
2
|
+
|
3
|
+
Now, this gem supports only minimal part of class file specification.
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
|
7
|
+
Generating
|
8
|
+
|
9
|
+
```rb
|
10
|
+
require 'jvm_bytecode'
|
11
|
+
|
12
|
+
class_file = JvmBytecode::ClassFile.new do
|
13
|
+
constant_pool do
|
14
|
+
utf8 'Sample'
|
15
|
+
utf8 'java/lang/Object'
|
16
|
+
|
17
|
+
add_class 'Sample'
|
18
|
+
|
19
|
+
add_method_ref(
|
20
|
+
add_class('java/lang/Object'),
|
21
|
+
add_name_and_type('<init>', '()V')
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
access_flag :super
|
26
|
+
|
27
|
+
this_class 'Sample'
|
28
|
+
super_class 'java/lang/Object'
|
29
|
+
|
30
|
+
method_definition do
|
31
|
+
name 'add'
|
32
|
+
descriptor '(II)I'
|
33
|
+
|
34
|
+
code do
|
35
|
+
max_stack 2
|
36
|
+
max_locals 3
|
37
|
+
|
38
|
+
iload_1
|
39
|
+
iload_2
|
40
|
+
iadd
|
41
|
+
ireturn
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
method_definition do
|
46
|
+
name '<init>'
|
47
|
+
descriptor '()V'
|
48
|
+
|
49
|
+
code do
|
50
|
+
max_stack 1
|
51
|
+
max_locals 1
|
52
|
+
|
53
|
+
aload_0
|
54
|
+
invokespecial 8
|
55
|
+
_return
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
File.open('Sample.class', 'wb') do |f|
|
61
|
+
f.write(class_file.bytecode)
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Decoding
|
66
|
+
|
67
|
+
```
|
68
|
+
irb(main):012:0* require 'jvm_bytecode'
|
69
|
+
=> true
|
70
|
+
irb(main):013:0> require 'pp'
|
71
|
+
=> true
|
72
|
+
irb(main):014:0> pp JvmBytecode::ClassFile.decode(File.open('Sample.class', 'rb')).to_hash
|
73
|
+
{:minor_version=>0,
|
74
|
+
:major_version=>52,
|
75
|
+
:constant_pool=>
|
76
|
+
[{:index=>1, :type=>"Utf8", :string=>"Sample"},
|
77
|
+
{:index=>2, :type=>"Utf8", :string=>"java/lang/Object"},
|
78
|
+
{:index=>3, :type=>"Class", :name_index=>1},
|
79
|
+
{:index=>4, :type=>"Class", :name_index=>2},
|
80
|
+
{:index=>5, :type=>"Utf8", :string=>"<init>"},
|
81
|
+
{:index=>6, :type=>"Utf8", :string=>"()V"},
|
82
|
+
{:index=>7, :type=>"NameAndType", :name_index=>5, :descriptor_index=>6},
|
83
|
+
{:index=>8, :type=>"MethodRef", :class_index=>4, :name_and_type_index=>7},
|
84
|
+
{:index=>9, :type=>"Utf8", :string=>"add"},
|
85
|
+
{:index=>10, :type=>"Utf8", :string=>"(II)I"},
|
86
|
+
{:index=>11, :type=>"Utf8", :string=>"Code"}],
|
87
|
+
:access_flag=>[:super],
|
88
|
+
:this_class=>3,
|
89
|
+
:super_class=>4,
|
90
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "jvm_bytecode"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jvm_bytecode/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jvm_bytecode"
|
8
|
+
spec.version = JvmBytecode::VERSION
|
9
|
+
spec.authors = ["bonono"]
|
10
|
+
spec.email = ["bonono.jp@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Tool for generating and decoding JVM bytecode}
|
13
|
+
spec.description = %q{Tool for generating and decoding JVM bytecode}
|
14
|
+
spec.homepage = "https://github.com/bonono/ruby-jvm-bytecode"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.required_ruby_version = '>= 2.1'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
end
|
data/lib/jvm_bytecode.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'jvm_bytecode/version'
|
2
|
+
require 'jvm_bytecode/extensions'
|
3
|
+
|
4
|
+
require 'jvm_bytecode/access_flag'
|
5
|
+
|
6
|
+
require 'jvm_bytecode/errors/constant_error'
|
7
|
+
require 'jvm_bytecode/errors/decode_error'
|
8
|
+
require 'jvm_bytecode/errors/opcode_error'
|
9
|
+
|
10
|
+
require 'jvm_bytecode/constants/constant'
|
11
|
+
require 'jvm_bytecode/constants/ref'
|
12
|
+
require 'jvm_bytecode/constants/class'
|
13
|
+
require 'jvm_bytecode/constants/method_ref'
|
14
|
+
require 'jvm_bytecode/constants/name_and_type'
|
15
|
+
require 'jvm_bytecode/constants/utf8'
|
16
|
+
|
17
|
+
require 'jvm_bytecode/instructions/instruction'
|
18
|
+
require 'jvm_bytecode/instructions/instruction_set'
|
19
|
+
|
20
|
+
require 'jvm_bytecode/attributes/attribute'
|
21
|
+
require 'jvm_bytecode/attributes/code'
|
22
|
+
require 'jvm_bytecode/attributes/source_file'
|
23
|
+
|
24
|
+
require 'jvm_bytecode/field'
|
25
|
+
require 'jvm_bytecode/method'
|
26
|
+
require 'jvm_bytecode/constant_pool'
|
27
|
+
require 'jvm_bytecode/class_file'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module AccessFlag
|
3
|
+
def all_access_flag
|
4
|
+
self.class::ACCESS_FLAGS
|
5
|
+
end
|
6
|
+
|
7
|
+
def access_flag(*flags)
|
8
|
+
@acc_flag = flags.map(&all_access_flag.method(:[])).reduce(0, &:|) if flags.any?
|
9
|
+
@acc_flag || 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_access_flag(acc_flag)
|
13
|
+
@acc_flag = acc_flag
|
14
|
+
end
|
15
|
+
|
16
|
+
def readable_access_flag
|
17
|
+
all_access_flag.select { |k, v| (@acc_flag & v) > 0 }.keys
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Attributes
|
3
|
+
class Attribute
|
4
|
+
using Extensions
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :attr_name, :locations
|
8
|
+
|
9
|
+
def define(name: nil, location:)
|
10
|
+
@attr_name = name || shortname.encode('UTF-8')
|
11
|
+
@locations = location
|
12
|
+
|
13
|
+
@@attributes ||= {}
|
14
|
+
@@attributes[@attr_name] = self
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch(attr_name)
|
18
|
+
@@attributes[attr_name] || raise(Errors::AttributeError, "#{attr_name} is not implemented")
|
19
|
+
end
|
20
|
+
|
21
|
+
def locatable_at(loc)
|
22
|
+
@@attributes.select { |_, v| v.locations.include?(loc) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def decode_serial(cp, io)
|
26
|
+
n = io.read(2).unpack('S>').first
|
27
|
+
Array.new(n) do
|
28
|
+
name_index = io.read(2).unpack('S>').first
|
29
|
+
fetch(cp.constant(name_index).to_s).new(cp).tap { |a| a.decode(io) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :cp
|
35
|
+
|
36
|
+
def initialize(cp)
|
37
|
+
@cp = cp
|
38
|
+
end
|
39
|
+
|
40
|
+
def additional_bytecode
|
41
|
+
raise NotImplementedError, "#{self.class}##{__method__} is not implemented!"
|
42
|
+
end
|
43
|
+
|
44
|
+
def bytecode
|
45
|
+
bc = additional_bytecode
|
46
|
+
[cp.utf8(self.class.attr_name), bc.length].pack('S>I>') + bc
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Attributes
|
3
|
+
class Code < Attribute
|
4
|
+
define location: [:method]
|
5
|
+
|
6
|
+
Instructions::Instruction.all.each do |i|
|
7
|
+
define_method(i.mnemonic) do |*args|
|
8
|
+
@instructions.push(i.new(cp)).last.args = args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(cp)
|
13
|
+
super(cp)
|
14
|
+
|
15
|
+
@max_stack = 0
|
16
|
+
@max_locals = 0
|
17
|
+
@instructions = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def max_stack(n)
|
21
|
+
@max_stack = n
|
22
|
+
end
|
23
|
+
|
24
|
+
def max_locals(n)
|
25
|
+
@max_locals = n
|
26
|
+
end
|
27
|
+
|
28
|
+
def additional_bytecode
|
29
|
+
code = @instructions.map(&:bytecode).join('')
|
30
|
+
[@max_stack, @max_locals, code.length].pack('S>2I>') + code + [0, 0].pack('S>2')
|
31
|
+
end
|
32
|
+
|
33
|
+
def decode(io)
|
34
|
+
io.read(4) # discard length
|
35
|
+
|
36
|
+
@max_stack, @max_locals, len = io.read(8).unpack('S>2I>')
|
37
|
+
|
38
|
+
@instructions.clear
|
39
|
+
while len > 0
|
40
|
+
opcode = io.read(1).unpack('C').first
|
41
|
+
inst = Instructions::Instruction.fetch(opcode)
|
42
|
+
@instructions << inst.new(cp).tap { |i| i.decode(io) }
|
43
|
+
|
44
|
+
len -= inst.size
|
45
|
+
end
|
46
|
+
|
47
|
+
io.read(4) # discard exceptions and attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_hash
|
51
|
+
{
|
52
|
+
type: self.class.name.split('::').last,
|
53
|
+
max_stack: @max_stack,
|
54
|
+
max_local_variables: @max_locals,
|
55
|
+
instructions: @instructions.map(&:to_hash)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Attributes
|
3
|
+
class SourceFile < Attribute
|
4
|
+
define location: [:class_file]
|
5
|
+
|
6
|
+
def filename(f)
|
7
|
+
@filename = cp.index_or_utf8(f)
|
8
|
+
end
|
9
|
+
|
10
|
+
def additional_bytecode
|
11
|
+
[@filename].pack('S>')
|
12
|
+
end
|
13
|
+
|
14
|
+
def decode(io)
|
15
|
+
io.read(4) # discard length
|
16
|
+
@filename = io.read(2).unpack('S>').first
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
class ClassFile
|
3
|
+
using Extensions
|
4
|
+
include AccessFlag
|
5
|
+
|
6
|
+
MAGIC_NUMBER = [0xCA, 0xFE, 0xBA, 0xBE].pack('C4').freeze
|
7
|
+
ACCESS_FLAGS = {
|
8
|
+
public: 0x0001,
|
9
|
+
final: 0x0010,
|
10
|
+
super: 0x0020,
|
11
|
+
interface: 0x0200,
|
12
|
+
abstract: 0x0400,
|
13
|
+
synthetic: 0x1000,
|
14
|
+
anotation: 0x2000,
|
15
|
+
enum: 0x4000
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.decode(io)
|
19
|
+
new.tap { |cf| cf.decode(io) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(&block)
|
23
|
+
@cp = ConstantPool.new
|
24
|
+
|
25
|
+
@minor_ver = 0
|
26
|
+
@major_ver = 52
|
27
|
+
|
28
|
+
@this_class = nil
|
29
|
+
@super_class = nil
|
30
|
+
|
31
|
+
@interfaces = []
|
32
|
+
@fields = []
|
33
|
+
@methods = []
|
34
|
+
@attributes = []
|
35
|
+
|
36
|
+
self.instance_eval(&block) if block_given?
|
37
|
+
end
|
38
|
+
|
39
|
+
def constant_pool(&block)
|
40
|
+
@cp.tap { |cp| cp.instance_eval(&block) if block_given? }
|
41
|
+
end
|
42
|
+
|
43
|
+
def minor_version(v)
|
44
|
+
@minor_ver = v
|
45
|
+
end
|
46
|
+
|
47
|
+
def major_version(v)
|
48
|
+
@major_ver = v
|
49
|
+
end
|
50
|
+
|
51
|
+
def this_class(name)
|
52
|
+
@this_class = @cp.class_index(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def super_class(name)
|
56
|
+
@super_class = @cp.class_index(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def new_method
|
60
|
+
Method.new(@cp)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_method(m)
|
64
|
+
@methods.push(m).last
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_definition(&block)
|
68
|
+
add_method(new_method).tap { |m| m.instance_eval(&block) if block_given?}
|
69
|
+
end
|
70
|
+
|
71
|
+
def source_file(filename)
|
72
|
+
@attributes << Attributes::SourceFile.new(@cp).tap do |a|
|
73
|
+
a.filename(filename)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def bytecode
|
78
|
+
version = [@minor_ver, @major_ver].pack('S>*')
|
79
|
+
classes = [access_flag, @this_class, @super_class].pack('S>*')
|
80
|
+
interfaces = ([@interfaces.length] + @interfaces).pack("S>*")
|
81
|
+
fields = @fields.join_bytecodes
|
82
|
+
methods = @methods.join_bytecodes
|
83
|
+
attributes = @attributes.join_bytecodes
|
84
|
+
|
85
|
+
MAGIC_NUMBER + version + @cp.bytecode + classes + interfaces + fields + methods + attributes
|
86
|
+
end
|
87
|
+
|
88
|
+
def decode(io)
|
89
|
+
raise Errors::DecodeError, 'Not class file' if io.read(4) != MAGIC_NUMBER
|
90
|
+
|
91
|
+
@minor_ver, @major_ver = io.read(4).unpack('S>2')
|
92
|
+
|
93
|
+
@cp.decode(io)
|
94
|
+
|
95
|
+
acc_flag, @this_class, @super_class = io.read(6).unpack('S>3')
|
96
|
+
set_access_flag(acc_flag)
|
97
|
+
|
98
|
+
if_count = io.read(2).unpack('S>').first
|
99
|
+
@interfaces = io.read(if_count * 2).unpack("S>#{if_count}")
|
100
|
+
|
101
|
+
@fields = Field.decode_serial(@cp, io)
|
102
|
+
@methods = Method.decode_serial(@cp, io)
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_hash
|
106
|
+
{
|
107
|
+
minor_version: @minor_ver,
|
108
|
+
major_version: @major_ver,
|
109
|
+
constant_pool: @cp.to_hash,
|
110
|
+
access_flag: readable_access_flag,
|
111
|
+
this_class: @this_class,
|
112
|
+
super_class: @super_class,
|
113
|
+
methods: @methods.map(&:to_hash)
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
class ConstantPool
|
3
|
+
using Extensions
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@constants = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def constant(index)
|
10
|
+
@constants[index - 1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def utf8(str)
|
14
|
+
index = nil
|
15
|
+
each_constants_of Constants::Utf8.tag do |utf8, i|
|
16
|
+
index = i if utf8.to_s == str
|
17
|
+
end
|
18
|
+
|
19
|
+
index || add(Constants::Utf8.new(str))
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_class(name)
|
23
|
+
add(Constants::Class.new(index_or_utf8(name)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_name_and_type(name, descriptor)
|
27
|
+
add(
|
28
|
+
Constants::NameAndType.new(index_or_utf8(name), index_or_utf8(descriptor))
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_method_ref(class_index, name_and_type_index)
|
33
|
+
add(Constants::MethodRef.new(class_index, name_and_type_index))
|
34
|
+
end
|
35
|
+
|
36
|
+
def class_index(name)
|
37
|
+
name_index = utf8(name)
|
38
|
+
|
39
|
+
index = nil
|
40
|
+
each_constants_of Constants::Class.tag do |klass, i|
|
41
|
+
index = i if klass.name_index == name_index
|
42
|
+
end
|
43
|
+
|
44
|
+
index || raise(Errors::ConstantError, "Class constant named \"#{name}\" doesn't exist")
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_constants_of(tag)
|
48
|
+
@constants.each.with_index do |const, i|
|
49
|
+
yield const, i + 1 if const.class.tag == tag
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add(const)
|
54
|
+
@constants.push(const).length
|
55
|
+
end
|
56
|
+
|
57
|
+
def index_or_utf8(v)
|
58
|
+
v.is_a?(String) ? utf8(v) : v.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def bytecode
|
62
|
+
@constants.join_bytecodes { @constants.length + 1 }
|
63
|
+
end
|
64
|
+
|
65
|
+
def decode(io)
|
66
|
+
entries = io.read(2).unpack('S>').first - 1
|
67
|
+
|
68
|
+
@constants.clear
|
69
|
+
entries.times do
|
70
|
+
tag = io.read(1).unpack('C').first
|
71
|
+
@constants << Constants::Constant.fetch(tag).decode(io)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_hash
|
76
|
+
@constants.map.with_index do |const, i|
|
77
|
+
{
|
78
|
+
index: i + 1,
|
79
|
+
type: const.class.name.split('::').last.gsub('Constant', '')
|
80
|
+
}.merge(const.to_hash)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Constants
|
3
|
+
class Class < Constant
|
4
|
+
define 7
|
5
|
+
|
6
|
+
attr_reader :name_index
|
7
|
+
|
8
|
+
def self.decode(io)
|
9
|
+
new(io.read(2).unpack('S>').first)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name_index)
|
13
|
+
@name_index = name_index
|
14
|
+
end
|
15
|
+
|
16
|
+
def additional_bytecode
|
17
|
+
[@name_index].pack('S>')
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{ name_index: @name_index }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Constants
|
3
|
+
class Constant
|
4
|
+
class << self
|
5
|
+
attr_reader :tag
|
6
|
+
|
7
|
+
def define(t)
|
8
|
+
@@constants ||= {}
|
9
|
+
@@constants[t] = self
|
10
|
+
|
11
|
+
@tag = t
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch(t)
|
15
|
+
@@constants[t] || raise(Errors::ConstantError, "Constant:#{t} is not implemented")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def bytecode
|
20
|
+
[self.class.tag].pack('C') + additional_bytecode
|
21
|
+
end
|
22
|
+
|
23
|
+
def additional_bytecode
|
24
|
+
''
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Constants
|
3
|
+
class NameAndType < Constant
|
4
|
+
define 12
|
5
|
+
|
6
|
+
attr_reader :name_index, :descriptor_index
|
7
|
+
|
8
|
+
def self.decode(io)
|
9
|
+
indexes = io.read(4).unpack('S>2')
|
10
|
+
new(*indexes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name_index, descriptor_index)
|
14
|
+
@name_index = name_index
|
15
|
+
@descriptor_index = descriptor_index
|
16
|
+
end
|
17
|
+
|
18
|
+
def additional_bytecode
|
19
|
+
[@name_index, @descriptor_index].pack('S>2')
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{
|
24
|
+
name_index: @name_index,
|
25
|
+
descriptor_index: @descriptor_index
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Constants
|
3
|
+
class Ref < Constant
|
4
|
+
attr_reader :class_index, :name_and_type_index
|
5
|
+
|
6
|
+
def self.decode(io)
|
7
|
+
indexes = io.read(4).unpack('S>2')
|
8
|
+
new(*indexes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(class_index, name_and_type_index)
|
12
|
+
@class_index = class_index
|
13
|
+
@name_and_type_index = name_and_type_index
|
14
|
+
end
|
15
|
+
|
16
|
+
def additional_bytecode
|
17
|
+
[@class_index, @name_and_type_index].pack('S>2')
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{
|
22
|
+
class_index: @class_index,
|
23
|
+
name_and_type_index: @name_and_type_index
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Constants
|
3
|
+
class Utf8 < Constant
|
4
|
+
define 1
|
5
|
+
|
6
|
+
def self.decode(io)
|
7
|
+
str = io.read(io.read(2).unpack('S>').first).force_encoding('UTF-8')
|
8
|
+
new(str)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(str)
|
12
|
+
raise "#{self.class} requires utf-8 string" unless str.encoding.to_s == 'UTF-8'
|
13
|
+
@str = str
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@str
|
18
|
+
end
|
19
|
+
|
20
|
+
def additional_bytecode
|
21
|
+
s = @str.dup.force_encoding('ASCII-8BIT')
|
22
|
+
[s.length].pack('S>') + s
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
{ string: @str }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Extensions
|
3
|
+
refine Array do
|
4
|
+
def join_bytecodes(template = 'S>')
|
5
|
+
[block_given? ? yield : length].pack('S>') + map(&:bytecode).join('')
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
refine Class do
|
10
|
+
def shortname
|
11
|
+
name.split('::').last
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
class Field
|
3
|
+
using Extensions
|
4
|
+
include AccessFlag
|
5
|
+
|
6
|
+
ACCESS_FLAGS = {
|
7
|
+
public: 0x0001,
|
8
|
+
private: 0x0002,
|
9
|
+
protected: 0x0004,
|
10
|
+
static: 0x0008,
|
11
|
+
final: 0x0010,
|
12
|
+
volatile: 0x0040,
|
13
|
+
transient: 0x0080,
|
14
|
+
synthetic: 0x1000,
|
15
|
+
enum: 0x4000
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.decode_serial(cp, io)
|
19
|
+
Array.new(io.read(2).unpack('S>').first) do
|
20
|
+
new(cp).tap { |f| f.decode(io) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(cp)
|
25
|
+
@cp = cp
|
26
|
+
@name = 0
|
27
|
+
@descriptor = 0
|
28
|
+
@attributes = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def name(n)
|
32
|
+
@name = @cp.index_or_utf8(n)
|
33
|
+
end
|
34
|
+
|
35
|
+
def descriptor(d)
|
36
|
+
@descriptor = @cp.index_or_utf8(d)
|
37
|
+
end
|
38
|
+
|
39
|
+
def bytecode
|
40
|
+
[access_flag, @name, @descriptor].pack('S>3') +
|
41
|
+
@attributes.join_bytecodes
|
42
|
+
end
|
43
|
+
|
44
|
+
def decode(io)
|
45
|
+
acc_flag, @name, @descriptor = io.read(6).unpack('S>3')
|
46
|
+
set_access_flag(acc_flag)
|
47
|
+
|
48
|
+
@attributes = Attributes::Attribute.decode_serial(@cp, io)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_hash
|
52
|
+
{}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Instructions
|
3
|
+
class Instruction
|
4
|
+
using Extensions
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :opcode, :size, :mnemonic
|
8
|
+
|
9
|
+
def format(opcode:, size: 1, mnemonic: nil)
|
10
|
+
@@instructions ||= {}
|
11
|
+
@@instructions[opcode] = self
|
12
|
+
|
13
|
+
@opcode = opcode
|
14
|
+
@size = size
|
15
|
+
@mnemonic = mnemonic || shortname.downcase
|
16
|
+
end
|
17
|
+
|
18
|
+
def all
|
19
|
+
@@instructions.values
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch(opcode)
|
23
|
+
@@instructions[opcode] || raise(Errors::OpcodeError, "#{sprintf('0x%02X', opcode)} is not implemented")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :cp
|
28
|
+
attr_accessor :args
|
29
|
+
|
30
|
+
def initialize(cp)
|
31
|
+
@cp = cp
|
32
|
+
@args = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def bytecode
|
36
|
+
[self.class.opcode].pack('C') + additional_bytecode
|
37
|
+
end
|
38
|
+
|
39
|
+
def additional_bytecode
|
40
|
+
''
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode(io)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
{
|
49
|
+
mnemonic: self.class.mnemonic,
|
50
|
+
opcode: self.class.opcode
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
module Instructions
|
3
|
+
4.times do |n|
|
4
|
+
# iload_(0|1|2|3)
|
5
|
+
Class.new(Instruction) do
|
6
|
+
format opcode: 0x1A + n, mnemonic: "iload_#{n}"
|
7
|
+
end
|
8
|
+
|
9
|
+
# aload_(0|1|2|3)
|
10
|
+
Class.new(Instruction) do
|
11
|
+
format opcode: 0x2A + n, mnemonic: "aload_#{n}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class IAdd < Instruction
|
16
|
+
format opcode: 0x60
|
17
|
+
end
|
18
|
+
|
19
|
+
class IReturn < Instruction
|
20
|
+
format opcode: 0xAC
|
21
|
+
end
|
22
|
+
|
23
|
+
class Return < Instruction
|
24
|
+
format opcode: 0xB1, mnemonic: '_return'
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
super.merge({ mnemonic: 'return' })
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvokeSpecial < Instruction
|
32
|
+
format opcode: 0xB7, size: 3
|
33
|
+
|
34
|
+
def additional_bytecode
|
35
|
+
[args.first].pack('S>')
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode(io)
|
39
|
+
super(io)
|
40
|
+
self.args = io.read(2).unpack('S>')
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_hash
|
44
|
+
super.merge({ ref: args.first })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module JvmBytecode
|
2
|
+
class Method
|
3
|
+
using Extensions
|
4
|
+
include AccessFlag
|
5
|
+
|
6
|
+
ACCESS_FLAGS = {
|
7
|
+
public: 0x0001,
|
8
|
+
private: 0x0002,
|
9
|
+
protected: 0x0004,
|
10
|
+
static: 0x0008,
|
11
|
+
final: 0x0010,
|
12
|
+
syncrhonized: 0x0020,
|
13
|
+
bridge: 0x0040,
|
14
|
+
varargs: 0x0080,
|
15
|
+
native: 0x0100,
|
16
|
+
abstract: 0x0400,
|
17
|
+
strict: 0x0800,
|
18
|
+
synthetic: 0x1000
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def self.decode_serial(cp, io)
|
22
|
+
Array.new(io.read(2).unpack('S>').first) do
|
23
|
+
new(cp).tap { |m| m.decode(io) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(cp)
|
28
|
+
@cp = cp
|
29
|
+
@name = 0
|
30
|
+
@descriptor = 0
|
31
|
+
@attributes = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def name(n)
|
35
|
+
@name = @cp.index_or_utf8(n)
|
36
|
+
end
|
37
|
+
|
38
|
+
def descriptor(d)
|
39
|
+
@descriptor = @cp.index_or_utf8(d)
|
40
|
+
end
|
41
|
+
|
42
|
+
def code(&block)
|
43
|
+
@attributes
|
44
|
+
.push(Attributes::Code.new(@cp))
|
45
|
+
.last
|
46
|
+
.instance_eval(&block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def bytecode
|
50
|
+
[access_flag, @name, @descriptor].pack('S>*') + @attributes.join_bytecodes
|
51
|
+
end
|
52
|
+
|
53
|
+
def decode(io)
|
54
|
+
acc_flag, @name, @descriptor = io.read(6).unpack('S>3')
|
55
|
+
set_access_flag(acc_flag)
|
56
|
+
|
57
|
+
@attributes = Attributes::Attribute.decode_serial(@cp, io)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_hash
|
61
|
+
{
|
62
|
+
name_index: @name,
|
63
|
+
descriptor_index: @descriptor,
|
64
|
+
access_flag: access_flag,
|
65
|
+
attributes: @attributes.map(&:to_hash)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jvm_bytecode
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bonono
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Tool for generating and decoding JVM bytecode
|
56
|
+
email:
|
57
|
+
- bonono.jp@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".editorconfig"
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- jvm_bytecode.gemspec
|
72
|
+
- lib/jvm_bytecode.rb
|
73
|
+
- lib/jvm_bytecode/access_flag.rb
|
74
|
+
- lib/jvm_bytecode/attributes/attribute.rb
|
75
|
+
- lib/jvm_bytecode/attributes/code.rb
|
76
|
+
- lib/jvm_bytecode/attributes/source_file.rb
|
77
|
+
- lib/jvm_bytecode/class_file.rb
|
78
|
+
- lib/jvm_bytecode/constant_pool.rb
|
79
|
+
- lib/jvm_bytecode/constants/class.rb
|
80
|
+
- lib/jvm_bytecode/constants/constant.rb
|
81
|
+
- lib/jvm_bytecode/constants/method_ref.rb
|
82
|
+
- lib/jvm_bytecode/constants/name_and_type.rb
|
83
|
+
- lib/jvm_bytecode/constants/ref.rb
|
84
|
+
- lib/jvm_bytecode/constants/utf8.rb
|
85
|
+
- lib/jvm_bytecode/errors/attribute_error.rb
|
86
|
+
- lib/jvm_bytecode/errors/constant_error.rb
|
87
|
+
- lib/jvm_bytecode/errors/decode_error.rb
|
88
|
+
- lib/jvm_bytecode/errors/opcode_error.rb
|
89
|
+
- lib/jvm_bytecode/extensions.rb
|
90
|
+
- lib/jvm_bytecode/field.rb
|
91
|
+
- lib/jvm_bytecode/instructions/instruction.rb
|
92
|
+
- lib/jvm_bytecode/instructions/instruction_set.rb
|
93
|
+
- lib/jvm_bytecode/method.rb
|
94
|
+
- lib/jvm_bytecode/version.rb
|
95
|
+
homepage: https://github.com/bonono/ruby-jvm-bytecode
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '2.1'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.4.5.1
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Tool for generating and decoding JVM bytecode
|
119
|
+
test_files: []
|