jclass makes it easy to generate and analyze Java class files in Common Lisp. Build a compiler backend or a JVM bytecode analyzer with jclass!
Java classes, fields, methods, annotations, and more are represented as Lisp objects for easy analysis and manipulation. jclass also automatically builds the constant pool for you (but you can still make the pool manually if you want!)
It's also possible to create custom class attributes with jclass. An attribute description DSL makes it easy to add new attributes. Great for targeting custom JVMs or experimental features.
jclass is feature complete and the API is mostly stable. jclass will be updated
with new JVM features whenever they are released, so do not (:use #:jclass)
in
your packages.
For in-depth information, see the manual.
;; Build the main method
(defparameter *main*
(make-instance 'jclass:method-info
:flags '(:public :static)
:name "main"
:descriptor "([Ljava/lang/String;)V" ; void (String[])
:attributes
(list
(make-instance 'jclass:code
:max-stack 2
:max-locals 1
:bytecode
`((:getstatic "java/lang/System" "out" "Ljava/io/PrintStream;")
(:ldc ,(jclass:make-string-info "Hello, world!"))
(:invokevirtual "java/io/PrintStream" "println" "(Ljava/lang/String;)V")
:return)
:exceptions '()
:attributes '()))))
;; Generate the class file
(with-open-file (stream "./Hello.class"
:direction :output
:element-type '(unsigned-byte 8))
(write-sequence
(jclass:java-class-bytes
(make-instance 'jclass:java-class
;; version 55.0 = Java 11
:major-version 55
:minor-version 0
:flags '(:public)
:name "Hello"
:parent "java/lang/Object"
:interfaces '()
:fields '()
:methods (list *main*)
:attributes '()))
stream))
The class file executes as expected:
$ java Hello
Hello, world!
Output from javap:
$ javap -v Hello.class
Classfile Hello.class
Last modified Aug 22, 2021; size 281 bytes
MD5 checksum b1d5aa6684ec1b281718b3cb249f21a0
public class Hello
minor version: 0
major version: 55
flags: (0x0001) ACC_PUBLIC
this_class: #1 // Hello
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
#1 = Class #9 // Hello
#2 = Class #10 // java/lang/Object
#3 = Utf8 main
#4 = Utf8 ([Ljava/lang/String;)V
#5 = Fieldref #11.#13 // java/lang/System.out:Ljava/io/PrintStream;
#6 = String #16 // Hello, world!
#7 = Methodref #17.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V
#8 = Utf8 Code
#9 = Utf8 Hello
#10 = Utf8 java/lang/Object
#11 = Class #12 // java/lang/System
#12 = Utf8 java/lang/System
#13 = NameAndType #14:#15 // out:Ljava/io/PrintStream;
#14 = Utf8 out
#15 = Utf8 Ljava/io/PrintStream;
#16 = Utf8 Hello, world!
#17 = Class #18 // java/io/PrintStream
#18 = Utf8 java/io/PrintStream
#19 = NameAndType #20:#21 // println:(Ljava/lang/String;)V
#20 = Utf8 println
#21 = Utf8 (Ljava/lang/String;)V
{
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Hello, world!
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
jclass is currently not in Quicklisp or Ultralisp.
Install by cloning to your ~/common-lisp/
or quicklisp/local-projects/
directories.
Then run the following:
(ql:register-local-projects)
(asdf:load-system "jclass")
If you have Roswell, you can install from Github.
$ ros install davidsun0/jclass
jclass should work on conforming Common Lisps with the following features:
char-code
andcode-char
must work with Unicode code points.- If not working with Unicode, the functions must use ASCII values.
Incompatibility with an implementation that meets these requirements should be considered a bug. Please report any issues.
Since jclass works at the binary level, it only performs structural validation, not semantic validation. For example, certain attributes and flags are incompatible. Some features may only work with certain class file versions. jclass does not ensure that a JVM will accept a generated class.
It is up to the user to correctly match class file features.
- 4.1 The ClassFile Structure
- 4.4 The Constant Pool
- 4.5 Fields
- 4.6 Methods
- 4.7 Attributes
- 4.7.2 ConstantValue
- 4.7.3 Code
- 4.7.4 StackMapTable
- 4.7.5 Exceptions
- 4.7.6 InnerClasses
- 4.7.7 EnclosingMethod
- 4.7.8 Synthetic
- 4.7.9 Signature
- 4.7.10 SourceFile
- 4.7.11 SourceDebugExtension
- 4.7.12 LineNumberTable
- 4.7.13 LocalVariableTable
- 4.7.14 LocalVariableTypeTable
- 4.7.15 Deprecated
- 4.7.16 RuntimeVisibleAnnotations
- 4.7.17 RuntimeInvisibleAnnotations
- 4.7.18 RuntimeVisibleParameterAnnotations
- 4.7.19 RuntimeInvisibleParameterAnnotations
- 4.7.20 RuntimeVisibleTypeAnnotaions
- 4.7.21 RuntimeInvisibleTypeAnnotations
- 4.7.22 AnnotationDefault
- 4.7.23 BootstrapMethods
- 4.7.24 MethodParameters
- 4.7.25 Module
- 4.7.26 ModulePackages
- 4.7.27 ModuleMainClass
- 4.7.28 NestHost
- 4.7.29 NestMembers
- 4.7.30 Record
- 4.7.31 PermittedSublcasses
- Bytecode instructions (205 / 205)
- Constants (21 / 21)
- Loads (33 / 33)
- Stores (33 / 33)
- Stack (9 / 9)
- Math (37 / 37)
- Conversions (15 / 15)
- Comparisons (19 / 19)
- References (18 / 18)
- Control (11 / 11)
- Extended (6 / 6)
- Reserved (3 / 3)
jclass is based off of the Java Virtual Machine Specification, and not the
Java Programming Language. As such, it can generate code that uses the
invokedynamic
instruction or use special characters in names.
The class file is represented as a tree of objects. Every named structure in the JVM Specification has an equivalent Lisp object.
There are functions to assemble and disassemble a java-class
object to
and from a .class
file. As fields, methods, and attributes are part of a
class, they cannot be serialized independently.
MIT