javaclass 0.4.1 → 0.4.2

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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +188 -194
  3. data/Readme.txt +33 -29
  4. data/examples/generate_class_lists.rb +16 -9
  5. data/history.txt +15 -4
  6. data/javaclass.gemspec +10 -8
  7. data/lib/generated/examples/chart_class_dependencies.txt +35 -0
  8. data/lib/generated/examples/chart_module_dependencies.txt +43 -0
  9. data/lib/generated/examples/check_interface_names.txt +36 -0
  10. data/lib/generated/examples/count_classes_in_modules.txt +31 -0
  11. data/lib/generated/examples/cumulative_dependencies.txt +28 -0
  12. data/lib/generated/examples/find_all_imported_types.txt +38 -0
  13. data/lib/generated/examples/find_incoming_dependency_graph.txt +73 -0
  14. data/lib/generated/examples/find_layers_of_modules.txt +70 -0
  15. data/lib/generated/examples/find_referenced_modules.txt +41 -0
  16. data/lib/generated/examples/find_unreferenced_classes.txt +66 -0
  17. data/lib/generated/examples/generate_class_lists.txt +53 -0
  18. data/lib/generated/examples/show_jar_api.txt +64 -0
  19. data/lib/generated/examples/simple_usage.txt +38 -0
  20. data/lib/javaclass/classfile/access_flag_constants.rb +32 -5
  21. data/lib/javaclass/classfile/access_flags.rb +39 -20
  22. data/lib/javaclass/classfile/attributes/attributes.rb +134 -0
  23. data/lib/javaclass/classfile/class_access_flags.rb +37 -0
  24. data/lib/javaclass/classfile/class_file_attributes.rb +62 -0
  25. data/lib/javaclass/classfile/class_version.rb +0 -1
  26. data/lib/javaclass/classfile/constant_pool.rb +17 -11
  27. data/lib/javaclass/classfile/constants/single_reference.rb +53 -0
  28. data/lib/javaclass/classfile/fields.rb +37 -0
  29. data/lib/javaclass/classfile/java_class_header.rb +22 -13
  30. data/lib/javaclass/classfile/java_class_header_shortcuts.rb +6 -2
  31. data/lib/javaclass/classfile/methods.rb +37 -0
  32. data/lib/javaclass/classlist/jar_searcher.rb +38 -4
  33. data/lib/javaclass/classpath/temporary_unpacker.rb +1 -1
  34. data/lib/javaclass/string_hexdump.rb +1 -1
  35. data/license.txt +7 -7
  36. data/test/data/access_flags/{AccessFlagsTestInner$1.class → AccessFlagsTestAnonym$1.class} +0 -0
  37. data/test/data/access_flags/{AccessFlagsTestInner.class → AccessFlagsTestAnonym.class} +0 -0
  38. data/test/data/access_flags/AccessFlagsTestAnonym.java +9 -0
  39. data/test/data/access_flags/AccessFlagsTestPublic_javap.txt +1 -0
  40. data/test/data/constant_pool/Java8_JavaFX_Animation$1_Tag15.class +0 -0
  41. data/test/data/constant_pool/Java9_Activation_module-info_Tag20.class +0 -0
  42. data/test/data/jar_searcher/JarClassListTest.jar +0 -0
  43. data/test/data/jar_searcher/PublicClass.java +44 -1
  44. data/test/data/jar_searcher/make.bat +1 -2
  45. data/test/{test_access_flags.rb → test_class_access_flags.rb} +91 -93
  46. data/test/test_class_file_attributes.rb +57 -0
  47. data/test/test_constant_pool.rb +31 -0
  48. data/test/test_jar_searcher.rb +40 -7
  49. data/test/test_javaclass_api.rb +2 -2
  50. data/test/test_string_hexdump.rb +4 -1
  51. data/test/ts_all_tests.rb +4 -2
  52. metadata +100 -116
  53. data/test/data/access_flags/AccessFlagsTestInner$2.class +0 -0
  54. data/test/data/access_flags/AccessFlagsTestInner.java +0 -13
@@ -0,0 +1,37 @@
1
+ require 'javaclass/classfile/access_flags'
2
+ require 'javaclass/classfile/class_format_error'
3
+
4
+ module JavaClass
5
+ module ClassFile
6
+
7
+ # The access flags of a class or interface.
8
+ # Author:: Peter Kofler
9
+ class ClassAccessFlags < AccessFlags
10
+
11
+ # Bitmask for unknown/not supported flags on classes.
12
+ ACC_OTHER = 0xffff ^ ACC_PUBLIC ^ ACC_STATIC ^ ACC_FINAL ^ ACC_SUPER ^ ACC_INTERFACE ^ ACC_ABSTRACT ^ ACC_SYNTHETIC ^ ACC_ENUM ^ ACC_ANNOTATION ^ ACC_MODULE
13
+
14
+ def initialize(flags)
15
+ super
16
+ assert_flags
17
+ end
18
+
19
+ def assert_flags
20
+ raise ClassFormatError, "inconsistent flags #{flags} (other value #{flags & ACC_OTHER})" if (flags & ACC_OTHER) != 0
21
+ end
22
+ private :assert_flags
23
+
24
+ # Is this class a purely abstract class (and not an interface)?
25
+ def abstract_class?
26
+ abstract? && !interface_class?
27
+ end
28
+
29
+ # Is this class an interface (and not an annotation)?
30
+ def interface_class?
31
+ interface? && !annotation?
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,62 @@
1
+ require 'javaclass/java_name'
2
+
3
+ module JavaClass
4
+ module ClassFile
5
+
6
+ # Class file attributes.
7
+ # Author:: Peter Kofler
8
+ class ClassFileAttributes
9
+
10
+ def initialize(attributes, this_class)
11
+ @attributes = attributes
12
+ @this_class = this_class
13
+ end
14
+
15
+ # Name of the source file.
16
+ def source_file
17
+ a = @attributes.with('SourceFile')
18
+ if a then a.source_file else '<not set>' end
19
+ end
20
+
21
+ # List of inner classes +Attributes::InnerClass+ with name and access flags.
22
+ def inner_classes
23
+ a = @attributes.with('InnerClasses')
24
+ if a then a.inner_classes else [] end
25
+ end
26
+
27
+ def inner_class?
28
+ if inner_classes.find { |inner| inner.class_name == @this_class }
29
+ true
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ # Defines an accessible inner class, which is a static inner class which is not synthetic.
36
+ def static_inner_class?
37
+ if inner_classes.find { |inner| inner.class_name == @this_class &&
38
+ inner.access_flags.static? &&
39
+ (!inner.access_flags.private? || inner.access_flags.protected?) }
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ def anonymous?
47
+ inner_class? && @this_class =~ /\$\d+$/
48
+ end
49
+
50
+ # Return outer class name for inner classes, or the current class name.
51
+ def outer_class
52
+ if inner_class?
53
+ JavaVMName.new(@this_class[/^[^$]+/])
54
+ else
55
+ @this_class
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -5,7 +5,6 @@ module JavaClass
5
5
 
6
6
  # Version of a class file.
7
7
  # Author:: Peter Kofler
8
- # See:: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#75883
9
8
  class ClassVersion # ZenTest FULL to find method to_s
10
9
 
11
10
  attr_reader :minor
@@ -13,19 +13,25 @@ module JavaClass
13
13
 
14
14
  # Types of constants by their +tag+.
15
15
  CONSTANT_TYPE_TAGS = {
16
- CLASS_TAG = 7 => Constants::ConstantClass,
17
- FIELD_TAG = 9 => Constants::ConstantField,
18
- METHOD_TAG = 10 => Constants::ConstantMethod,
16
+ CLASS_TAG = 7 => Constants::ConstantClass,
17
+ FIELD_TAG = 9 => Constants::ConstantField,
18
+ METHOD_TAG = 10 => Constants::ConstantMethod,
19
19
  INTERFACE_METHOD_TAG = 11 => Constants::ConstantInterfaceMethod,
20
- STRING_TAG = 8 => Constants::ConstantString,
21
- INT_TAG = 3 => Constants::ConstantInt,
22
- FLOAT_TAG = 4 => Constants::ConstantFloat,
23
- LONG_TAG = 5 => Constants::ConstantLong,
24
- DOUBLE_TAG = 6 => Constants::ConstantDouble,
25
- NAME_AND_TYPE_TAG = 12 => Constants::ConstantNameAndType,
26
- ASCIZ_TAG = 1 => Constants::ConstantAsciz,
20
+ STRING_TAG = 8 => Constants::ConstantString,
21
+ INT_TAG = 3 => Constants::ConstantInt,
22
+ FLOAT_TAG = 4 => Constants::ConstantFloat,
23
+ LONG_TAG = 5 => Constants::ConstantLong,
24
+ DOUBLE_TAG = 6 => Constants::ConstantDouble,
25
+ NAME_AND_TYPE_TAG = 12 => Constants::ConstantNameAndType,
26
+ ASCIZ_TAG = 1 => Constants::ConstantAsciz,
27
+ # Java 1.7
28
+ METHOD_HANDLE_TAG = 15 => Constants::ConstantMethodHandle,
29
+ METHOD_TYPE_TAG = 16 => Constants::ConstantMethodType,
30
+ INVOKE_DYNAMIC_TAG = 18 => Constants::ConstantInvokeDynamic,
31
+ # Java 9
32
+ MODULE_TAG = 19 => Constants::ConstantModule,
33
+ PACKAGE_TAG = 20 => Constants::ConstantPackage,
27
34
  }
28
- # TODO Java7/Java8 JDK, implement 3 new tags, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
29
35
 
30
36
  # Size of the whole constant pool in bytes.
31
37
  attr_reader :size
@@ -68,6 +68,59 @@ module JavaClass
68
68
  alias string_value first_value
69
69
  end
70
70
 
71
+ class ConstantMethodHandle < SingleReference # ZenTest SKIP
72
+ alias method_handle_index first_index
73
+
74
+ def initialize(pool, data, start)
75
+ super(pool, data, start)
76
+
77
+ @kind = data.u1(start+1)
78
+ @first_index = data.u2(start+2)
79
+ @size = 4
80
+ end
81
+ alias method_handle_value first_value
82
+ end
83
+
84
+ class ConstantMethodType < SingleReference # ZenTest SKIP
85
+ alias method_type_index first_index
86
+
87
+ def initialize(pool, data, start)
88
+ super(pool, data, start)
89
+ end
90
+ alias method_type_value first_value
91
+ end
92
+
93
+ class ConstantInvokeDynamic < SingleReference # ZenTest SKIP
94
+ alias name_and_type_index first_index
95
+
96
+ def initialize(pool, data, start)
97
+ super(pool, data, start)
98
+
99
+ @bootstrap_method_attr_index = data.u2(start+1)
100
+ @first_index = data.u2(start+3)
101
+ @size = 5
102
+ end
103
+ alias name_and_type_value first_value
104
+ end
105
+
106
+ class ConstantModule < SingleReference # ZenTest SKIP
107
+ alias name_index first_index
108
+
109
+ def initialize(pool, data, start)
110
+ super(pool, data, start)
111
+ end
112
+ alias name_value first_value
113
+ end
114
+
115
+ class ConstantPackage < SingleReference # ZenTest SKIP
116
+ alias name_index first_index
117
+
118
+ def initialize(pool, data, start)
119
+ super(pool, data, start)
120
+ end
121
+ alias name_value first_value
122
+ end
123
+
71
124
  end
72
125
  end
73
126
  end
@@ -0,0 +1,37 @@
1
+ require 'javaclass/classfile/attributes/attributes'
2
+
3
+ module JavaClass
4
+ module ClassFile
5
+
6
+ # Container of the fields - skips the fields for now.
7
+ # Author:: Peter Kofler
8
+ class Fields # :nodoc:
9
+
10
+ # Size of the whole fields structure in bytes.
11
+ attr_reader :size
12
+
13
+ # Parse the field structure from the bytes _data_ beginning at position _start_.
14
+ def initialize(data, start, constant_pool)
15
+ count = data.u2(start)
16
+ @size = 2
17
+
18
+ (1..count).each do |i|
19
+ # TODO Implement parsing of fields into Field class
20
+
21
+ # access_flags = data.u2(start + @size) # later ... FieldAccessFlag.new(data, start + @size)
22
+ # @size += 2
23
+ # name_index = data.u2(start + @size) # later ... get from ConstantPool
24
+ # @size += 2
25
+ # descriptor_index = data.u2(start + @size) # later ... get from ConstantPool
26
+ # @size += 2
27
+ @size += 6
28
+
29
+ attributes = Attributes::Attributes.new(data, start + @size, constant_pool)
30
+ @size += attributes.size
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -2,8 +2,12 @@ require 'javaclass/string_ux'
2
2
  require 'javaclass/classfile/class_magic'
3
3
  require 'javaclass/classfile/class_version'
4
4
  require 'javaclass/classfile/constant_pool'
5
+ require 'javaclass/classfile/class_access_flags'
6
+ require 'javaclass/classfile/fields'
7
+ require 'javaclass/classfile/methods'
8
+ require 'javaclass/classfile/attributes/attributes'
9
+ require 'javaclass/classfile/class_file_attributes'
5
10
  require 'javaclass/classfile/references'
6
- require 'javaclass/classfile/access_flags'
7
11
  require 'javaclass/java_name'
8
12
 
9
13
  module JavaClass
@@ -19,7 +23,7 @@ module JavaClass
19
23
  # Parse and disassemble Java class files, similar to the +javap+ command.
20
24
  # Provides all information of a Java class file. This is just a container for all kind of
21
25
  # specialised elements. The constuctor parses and creates all contained elements.
22
- # See:: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
26
+ # See:: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
23
27
  # See:: {en.wikipedia.org/wiki/Class}[http://en.wikipedia.org/wiki/Class_(file_format)]
24
28
  # Author:: Peter Kofler
25
29
  class JavaClassHeader
@@ -30,25 +34,29 @@ module JavaClass
30
34
  attr_reader :access_flags
31
35
  attr_reader :references
32
36
  attr_reader :interfaces
37
+ attr_reader :attributes
33
38
 
34
39
  # Create a header with the binary _data_ from the class file.
35
40
  def initialize(data)
41
+ pos = 0
36
42
 
37
43
  # ClassFile {
38
44
  # u4 magic;
39
45
  @magic = ClassMagic.new(data)
46
+ pos += 4
40
47
 
41
48
  # u2 minor_version;
42
49
  # u2 major_version;
43
50
  @version = ClassVersion.new(data)
51
+ pos += 4
44
52
 
45
53
  # u2 constant_pool_count;
46
54
  # cp_info constant_pool[constant_pool_count-1];
47
55
  @constant_pool = ConstantPool.new(data)
48
- pos = 8 + @constant_pool.size
56
+ pos += @constant_pool.size
49
57
 
50
58
  # u2 access_flags;
51
- @access_flags = AccessFlags.new(data, pos)
59
+ @access_flags = ClassAccessFlags.new(data.u2(pos))
52
60
  pos += 2
53
61
 
54
62
  # u2 this_class;
@@ -65,21 +73,22 @@ module JavaClass
65
73
  @interfaces = data.u2rep(count, pos + 2).collect { |i| @constant_pool.class_item(i) }
66
74
  pos += 2 + count*2
67
75
 
68
- # TODO Implement parsing of fields and methods of the JVM spec
69
76
  # u2 fields_count;
70
77
  # field_info fields[fields_count];
71
- # count = data.u2(pos)
72
- # @fields = data.u2rep(count, pos + 2).collect { |i| @constant_pool.field_item(i) }
73
- # pos += 2 + count*2
74
-
78
+ @fields = Fields.new(data, pos, @constant_pool)
79
+ pos += @fields.size
80
+
75
81
  # u2 methods_count;
76
82
  # method_info methods[methods_count];
77
- # count = data.u2(pos)
78
- # @methods = data.u2rep(count, pos + 2).collect { |i| @constant_pool.method_item(i) }
79
- # pos += 2 + count*2
83
+ @methods = Methods.new(data, pos, @constant_pool)
84
+ pos += @methods.size
80
85
 
81
86
  # u2 attributes_count;
82
87
  # attribute_info attributes[attributes_count];
88
+ attr = Attributes::Attributes.new(data, pos, @constant_pool)
89
+ pos += attr.size
90
+ @attributes = ClassFileAttributes.new(attr, this_class)
91
+
83
92
  # }
84
93
  @references = References.new(@constant_pool, @this_class_idx)
85
94
 
@@ -113,7 +122,7 @@ module JavaClass
113
122
  ext = super_class ? " extends #{super_class.to_classname}" : ''
114
123
  int = !@interfaces.empty? ? " implements #{@interfaces.join(',')}" : ''
115
124
  d << "#{mod}class #{this_class.to_classname}#{ext}#{int}"
116
- # d << " SourceFile: \"#{read from LineNumberTable?}\""
125
+ d << " SourceFile: \"#{attributes.source_file}\""
117
126
  d += @version.dump
118
127
  d += @constant_pool.dump
119
128
  d << ''
@@ -5,14 +5,18 @@ module JavaClass
5
5
 
6
6
  # Is this class an interface (and not an annotation)?
7
7
  def interface?
8
- access_flags.interface? && !access_flags.annotation?
8
+ access_flags.interface_class?
9
9
  end
10
10
 
11
11
  # Is this class an abstract class (and not an interface)?
12
12
  def abstract_class?
13
- access_flags.abstract? && !access_flags.interface?
13
+ access_flags.abstract_class?
14
14
  end
15
15
 
16
+ def inner?
17
+ attributes.inner?
18
+ end
19
+
16
20
  end
17
21
 
18
22
  # TODO add tests for this
@@ -0,0 +1,37 @@
1
+ require 'javaclass/classfile/attributes/attributes'
2
+
3
+ module JavaClass
4
+ module ClassFile
5
+
6
+ # Container of the methods - skips the fields for now.
7
+ # Author:: Peter Kofler
8
+ class Methods # :nodoc:
9
+
10
+ # Size of the whole methods structure in bytes.
11
+ attr_reader :size
12
+
13
+ # Parse the method structure from the bytes _data_ beginning at position _start_.
14
+ def initialize(data, start, constant_pool)
15
+ count = data.u2(start)
16
+ @size = 2
17
+
18
+ (1..count).each do |i|
19
+ # TODO Implement parsing of methods into Method class
20
+
21
+ # access_flags = data.u2(start + @size) # later ... MethodAccessFlag.new(data, start + @size)
22
+ # @size += 2
23
+ # name_index = data.u2(start + @size) # later ... get from ConstantPool
24
+ # @size += 2
25
+ # descriptor_index = data.u2(start + @size) # later ... get from ConstantPool
26
+ # @size += 2
27
+ @size += 6
28
+
29
+ attributes = Attributes::Attributes.new(data, start + @size, constant_pool)
30
+ @size += attributes.size
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -47,16 +47,49 @@ module JavaClass
47
47
  # Return true if the _classfile_ in the given _classpath_ is public. This is expensive because the JAR file is opened and the
48
48
  # _classfile_ is extracted and read.
49
49
  def public?(classpath, classfile)
50
+ # temporal hack, store @classpath and @header
51
+ @classpath = classpath
50
52
  begin
51
- header = ClassFile::JavaClassHeader.new(classpath.load_binary(classfile))
53
+ @header = ClassFile::JavaClassHeader.new(classpath.load_binary(classfile))
52
54
  rescue JavaClass::ClassFile::ClassFormatError => ex
53
55
  ex.add_classname(classfile, classpath.to_s)
54
56
  raise ex
55
57
  end
56
- header.magic.check("invalid java class #{classfile}")
57
- header.access_flags.accessible?
58
+ @header.magic.check("invalid java class #{classfile}")
59
+ @header.access_flags.accessible?
58
60
  end
59
61
 
62
+ def accessible?
63
+ if @skip_package_classes && !@header.access_flags.accessible?
64
+ # not public and we only want public
65
+ false
66
+
67
+ elsif @skip_inner_classes || !@header.attributes.inner_class?
68
+ # no inner classes have been collected, everything is accessible due its public flag
69
+ # or this is not an inner class, so everything is OK
70
+ true
71
+
72
+ # it is an inner class
73
+
74
+ elsif @header.access_flags.synthetic? || @header.attributes.anonymous?
75
+ # the inner class is anonymous or synthetic, not accessible
76
+ false
77
+
78
+ elsif !@header.attributes.static_inner_class?
79
+ # must be static (and not private nor protected) to be accessible
80
+ false
81
+
82
+ elsif !@skip_package_classes
83
+ # inner class is public or package access, OK
84
+ true
85
+
86
+ else
87
+ # inner class is public, but parent class might be package only
88
+ public?(@classpath, @header.attributes.outer_class.to_class_file)
89
+
90
+ end
91
+ end
92
+
60
93
  # Compile the class list for the given _version_ of Java. This searches the _path_ for zips and JARs
61
94
  # and adds them to the given _list_ of found classes. _version_ is a number >= 0, e.g. 2 for JDK 1.2.
62
95
  # _list_ must provide a <code>add_class(entry, is_public, version)</code> method.
@@ -72,7 +105,8 @@ module JavaClass
72
105
  def add_list_from_classpath(version, classpath, list)
73
106
  filter_classes(classpath.names).each do |entry|
74
107
  is_public = public?(classpath, entry)
75
- next if @skip_package_classes && !is_public
108
+ next if !accessible?
109
+
76
110
  list.add_class(entry, is_public, version) if list
77
111
  yield(entry, is_public, version) if block_given?
78
112
  end