While attending university for my bachelor's degree at around mid 2012 (according to an on-topic question of mine on Stack Overflow), I seemed to be mildly obsessed with Java's bytecode. I'm not exactly sure why, but trying to parse class files myself intrigued me. So I started this project and invested time in studying the respective chapter 4 of the JVM specification.
Running the project (the main class de.xehpuk.disassembler.reader.ClassFileReader disassembling itself) currently outputs this:
majorVersion = 51
minorVersion = 0
thisClass = de.xehpuk.disassembler.reader.ClassFileReader
superClass = de.xehpuk.disassembler.reader.Reader
interfaces: [none]
accessFlags: public
constant_pool:
#001 = Methodref de.xehpuk.disassembler.reader.Reader.<init>(java.io.DataInput)
#002 = Class de.xehpuk.disassembler.reader.Reader
#003 = NameAndType <init>(java.io.DataInput)
#004 = Utf8 de/xehpuk/disassembler/reader/Reader
#005 = Utf8 <init>
#006 = Utf8 (Ljava/io/DataInput;)V
#007 = Methodref de.xehpuk.disassembler.reader.ClassFileReader.readInt()
#008 = Class de.xehpuk.disassembler.reader.ClassFileReader
#009 = NameAndType readInt()
#010 = Utf8 de/xehpuk/disassembler/reader/ClassFileReader
#011 = Utf8 readInt
#012 = Utf8 ()I
#013 = Class de.xehpuk.disassembler.ClassFile
#014 = Utf8 de/xehpuk/disassembler/ClassFile
#015 = Integer -889275714
#016 = Class de.xehpuk.disassembler.exceptions.IllegalMagicException
#017 = Utf8 de/xehpuk/disassembler/exceptions/IllegalMagicException
#018 = Methodref de.xehpuk.disassembler.exceptions.IllegalMagicException.<init>(int)
#019 = NameAndType <init>(int)
#020 = Utf8 (I)V
#021 = Methodref de.xehpuk.disassembler.reader.ClassFileReader.readClassFileHead()
#022 = NameAndType readClassFileHead()
#023 = Utf8 readClassFileHead
#024 = Utf8 ()Lde/xehpuk/disassembler/ClassFileHead;
#025 = Methodref de.xehpuk.disassembler.ClassFileHead.getConstantPool()
#026 = Class de.xehpuk.disassembler.ClassFileHead
#027 = NameAndType getConstantPool()
#028 = Utf8 de/xehpuk/disassembler/ClassFileHead
#029 = Utf8 getConstantPool
#030 = Utf8 ()Lde/xehpuk/disassembler/constantpool/ConstantPool;
#031 = Methodref de.xehpuk.disassembler.reader.ClassFileReader.readClassFileBody(de.xehpuk.disassembler.constantpool.ConstantPool)
#032 = NameAndType readClassFileBody(de.xehpuk.disassembler.constantpool.ConstantPool)
#033 = Utf8 readClassFileBody
#034 = Utf8 (Lde/xehpuk/disassembler/constantpool/ConstantPool;)Lde/xehpuk/disassembler/ClassFileBody;
#035 = Methodref de.xehpuk.disassembler.ClassFile.<init>(de.xehpuk.disassembler.ClassFileHead, de.xehpuk.disassembler.ClassFileBody)
#036 = NameAndType <init>(de.xehpuk.disassembler.ClassFileHead, de.xehpuk.disassembler.ClassFileBody)
#037 = Utf8 (Lde/xehpuk/disassembler/ClassFileHead;Lde/xehpuk/disassembler/ClassFileBody;)V
#038 = Class de.xehpuk.disassembler.reader.ClassFileHeadReader
#039 = Utf8 de/xehpuk/disassembler/reader/ClassFileHeadReader
#040 = Methodref de.xehpuk.disassembler.reader.ClassFileHeadReader.<init>(java.io.DataInput)
#041 = Methodref de.xehpuk.disassembler.reader.ClassFileHeadReader.read()
#042 = NameAndType read()
#043 = Utf8 read
#044 = Class de.xehpuk.disassembler.reader.ClassFileBodyReader
#045 = Utf8 de/xehpuk/disassembler/reader/ClassFileBodyReader
#046 = Methodref de.xehpuk.disassembler.reader.ClassFileBodyReader.<init>(java.io.DataInput, de.xehpuk.disassembler.constantpool.ConstantPool)
#047 = NameAndType <init>(java.io.DataInput, de.xehpuk.disassembler.constantpool.ConstantPool)
#048 = Utf8 (Ljava/io/DataInput;Lde/xehpuk/disassembler/constantpool/ConstantPool;)V
#049 = Methodref de.xehpuk.disassembler.reader.ClassFileBodyReader.read()
#050 = NameAndType read()
#051 = Utf8 ()Lde/xehpuk/disassembler/ClassFileBody;
#052 = Fieldref System.java.io.PrintStream err
#053 = Class System
#054 = NameAndType java.io.PrintStream err
#055 = Utf8 java/lang/System
#056 = Utf8 err
#057 = Utf8 Ljava/io/PrintStream;
#058 = String "Usage: java -jar disassembler.jar <class-file>"
#059 = Utf8 Usage: java -jar disassembler.jar <class-file>
#060 = Methodref java.io.PrintStream.println(String)
#061 = Class java.io.PrintStream
#062 = NameAndType println(String)
#063 = Utf8 java/io/PrintStream
#064 = Utf8 println
#065 = Utf8 (Ljava/lang/String;)V
#066 = Methodref System.exit(int)
#067 = NameAndType exit(int)
#068 = Utf8 exit
#069 = Class java.io.DataInputStream
#070 = Utf8 java/io/DataInputStream
#071 = Class java.io.FileInputStream
#072 = Utf8 java/io/FileInputStream
#073 = Methodref java.io.FileInputStream.<init>(String)
#074 = NameAndType <init>(String)
#075 = Methodref java.io.DataInputStream.<init>(java.io.InputStream)
#076 = NameAndType <init>(java.io.InputStream)
#077 = Utf8 (Ljava/io/InputStream;)V
#078 = Methodref de.xehpuk.disassembler.reader.ClassFileReader.<init>(java.io.DataInput)
#079 = Methodref de.xehpuk.disassembler.reader.ClassFileReader.read()
#080 = NameAndType read()
#081 = Utf8 ()Lde/xehpuk/disassembler/ClassFile;
#082 = Methodref de.xehpuk.disassembler.ClassFile.dump(int)
#083 = NameAndType dump(int)
#084 = Utf8 dump
#085 = Methodref java.io.DataInputStream.close()
#086 = NameAndType close()
#087 = Utf8 close
#088 = Utf8 ()V
#089 = Class Throwable
#090 = Utf8 java/lang/Throwable
#091 = Methodref Throwable.addSuppressed(Throwable)
#092 = NameAndType addSuppressed(Throwable)
#093 = Utf8 addSuppressed
#094 = Utf8 (Ljava/lang/Throwable;)V
#095 = Utf8 Code
#096 = Utf8 LineNumberTable
#097 = Utf8 LocalVariableTable
#098 = Utf8 this
#099 = Utf8 Lde/xehpuk/disassembler/reader/ClassFileReader;
#100 = Utf8 in
#101 = Utf8 Ljava/io/DataInput;
#102 = Utf8 magic
#103 = Utf8 I
#104 = Utf8 head
#105 = Utf8 Lde/xehpuk/disassembler/ClassFileHead;
#106 = Utf8 body
#107 = Utf8 Lde/xehpuk/disassembler/ClassFileBody;
#108 = Utf8 StackMapTable
#109 = Utf8 Exceptions
#110 = Class java.io.IOException
#111 = Utf8 java/io/IOException
#112 = Utf8 constantPool
#113 = Utf8 Lde/xehpuk/disassembler/constantpool/ConstantPool;
#114 = Utf8 main
#115 = Utf8 ([Ljava/lang/String;)V
#116 = Utf8 reader
#117 = Utf8 file
#118 = Utf8 Lde/xehpuk/disassembler/ClassFile;
#119 = Utf8 Ljava/io/DataInputStream;
#120 = Utf8 args
#121 = Utf8 [Ljava/lang/String;
#122 = Class [Ljava.lang.String;
#123 = Utf8 ()Ljava/lang/Object;
#124 = Utf8 Signature
#125 = Utf8 Lde/xehpuk/disassembler/reader/Reader<Lde/xehpuk/disassembler/ClassFile;>;
#126 = Utf8 SourceFile
#127 = Utf8 ClassFileReader.java
fields: [none]
methods:
public void <init>(java.io.DataInput)
Code:
2, 2
Instructions:
ALOAD_0
ALOAD_1
INVOKESPECIAL
RETURN
LineNumberTable:
0, 19
5, 20
LocalVariableTable:
0, 6, this, Lde/xehpuk/disassembler/reader/ClassFileReader;, 0
0, 6, in, Ljava/io/DataInput;, 1
public de.xehpuk.disassembler.ClassFile read()
Code:
4, 4
Instructions:
ALOAD_0
INVOKEVIRTUAL
ISTORE_1
ILOAD_1
LDC
IF_ICMPEQ
NEW
DUP
ILOAD_1
INVOKESPECIAL
ATHROW
ALOAD_0
INVOKESPECIAL
ASTORE_2
ALOAD_0
ALOAD_2
INVOKEVIRTUAL
INVOKESPECIAL
ASTORE_3
NEW
DUP
ALOAD_2
ALOAD_3
INVOKESPECIAL
ARETURN
LineNumberTable:
0, 24
5, 25
11, 26
20, 28
25, 29
34, 30
LocalVariableTable:
0, 44, this, Lde/xehpuk/disassembler/reader/ClassFileReader;, 0
5, 39, magic, I, 1
25, 19, head, Lde/xehpuk/disassembler/ClassFileHead;, 2
34, 10, body, Lde/xehpuk/disassembler/ClassFileBody;, 3
UnrecognizedAttribute=StackMapTable
Exceptions:
java.io.IOException
private de.xehpuk.disassembler.ClassFileHead readClassFileHead()
Code:
3, 1
Instructions:
NEW
DUP
ALOAD_0
INVOKESPECIAL
INVOKEVIRTUAL
ARETURN
LineNumberTable:
0, 34
LocalVariableTable:
0, 12, this, Lde/xehpuk/disassembler/reader/ClassFileReader;, 0
Exceptions:
java.io.IOException
private de.xehpuk.disassembler.ClassFileBody readClassFileBody(de.xehpuk.disassembler.constantpool.ConstantPool)
Code:
4, 2
Instructions:
NEW
DUP
ALOAD_0
ALOAD_1
INVOKESPECIAL
INVOKEVIRTUAL
ARETURN
LineNumberTable:
0, 38
LocalVariableTable:
0, 13, this, Lde/xehpuk/disassembler/reader/ClassFileReader;, 0
0, 13, constantPool, Lde/xehpuk/disassembler/constantpool/ConstantPool;, 1
Exceptions:
java.io.IOException
public static varargs void main(String[])
Code:
6, 4
Instructions:
ALOAD_0
ARRAYLENGTH
IFNE
GETSTATIC
LDC
INVOKEVIRTUAL
ICONST_M1
INVOKESTATIC
NEW
DUP
NEW
DUP
ALOAD_0
ICONST_0
AALOAD
INVOKESPECIAL
INVOKESPECIAL
ASTORE_1
NEW
DUP
ALOAD_1
INVOKESPECIAL
ASTORE_2
ALOAD_2
INVOKEVIRTUAL
ASTORE_3
ALOAD_3
ICONST_1
INVOKEVIRTUAL
ALOAD_1
INVOKEVIRTUAL
GOTO
ASTORE_2
ALOAD_1
INVOKEVIRTUAL
GOTO
ASTORE_3
ALOAD_2
ALOAD_3
INVOKEVIRTUAL
ALOAD_2
ATHROW
RETURN
ExceptionTable:
35, 54, 61, Throwable
62, 66, 69, Throwable
LineNumberTable:
0, 42
5, 43
13, 44
17, 46
35, 47
44, 48
49, 49
54, 50
61, 46
77, 51
LocalVariableTable:
44, 10, reader, Lde/xehpuk/disassembler/reader/ClassFileReader;, 2
49, 5, file, Lde/xehpuk/disassembler/ClassFile;, 3
35, 42, in, Ljava/io/DataInputStream;, 1
0, 78, args, [Ljava/lang/String;, 0
UnrecognizedAttribute=StackMapTable
Exceptions:
java.io.IOException
public bridge Object read()
Code:
1, 1
Instructions:
ALOAD_0
INVOKEVIRTUAL
ARETURN
LineNumberTable:
0, 17
LocalVariableTable:
0, 5, this, Lde/xehpuk/disassembler/reader/ClassFileReader;, 0
Exceptions:
java.io.IOException
attributes:
Signature=Lde/xehpuk/disassembler/reader/Reader<Lde/xehpuk/disassembler/ClassFile;>;
SourceFile=ClassFileReader.java
I'm honestly quite amazed by how well this works, since I've never really learned the theoretical aspects of parsers and I couldn't remember the state of the project. An untrained eye may think that this is the output of the original javap.
With 11 TODOs (and certainly some hidden ones), it's definitely not finished. But who knows what the future holds…