-
Notifications
You must be signed in to change notification settings - Fork 30
Home
This page is an introduction to the byte code generation library "Cafebabe". Rather than exposing an API-like reference, we present the capabilities of the library through examples.
Other pages of interest are:
- a description of the available abstract bytecodes in Cafebabe
- full examples of class generation
- history of changes (incomplete)
git clone git://github.com/psuter/cafebabe.git
cd cafebabe
sbt package
This should produce a .jar(the output of the last command will tell you its location) that you can then copy into the lib/ directory of your sbt project.
Run:
sbt doc
firefox target/scala-*/api/index.html
You can also generate a jar with the documentation, for use with Eclipse for example:
sbt package-doc
Type | in Java code | in class files |
---|---|---|
Void | void |
V |
Integer | int |
I |
Boolean | boolean |
Z |
Byte | byte |
B |
Single precision floating point | float |
F |
Double precision floating point | double |
D |
Long integer | long |
J |
Short integer | short |
S |
Character | char |
C |
Object reference | package.ClassName |
Lpackage/ClassName;
|
Array | type[] |
[type |
Examples | ||
Byte array | byte[] |
[B |
Array of arrays of string | String[][] |
[[Ljava/lang/String; |
String [][]
Method signatures are written as the concatenation of the parameters in parentheses (without any separator), followed by the return type. For instance:
String repString(int times, String original) { ... }
...has the signature (ILjava/lang/String;)Ljava/lang/String;
, and:
int foo(double d, boolean test, byte b) { ... }
...has the signature (DZB)I
.
To create a handler to a class file representation, simply write:
val classFile = new ClassFile("TestClass", None)
...where the first parameter is the (fully qualified) class name, and the second one is the name of a potential parent class wrapped in an Option
value. Use:
val classFile = new ClassFile("TestSubClass", Some("TestClass"))
...to specify a parent class different from the default Java java.lang.Object
class.
If the class file was generated from compiling an actual source file, you can attach that information to it:
classFile.setSourceFile("MyProg.mylang")
Once all the fields, methods and code chunks for the methods have been set, you can write the class file itself to the disk by using:
classFile.writeToFile("./Test.class")
Note that to comply with the JVM specification, the file name and path should match the qualified class name in the standard way (subdirectories represent packages, etc.).
To add a field to a class, use the following method:
val fh: FieldHandler = classFile.addField("Ljava/lang/String;", "name")
...where the first argument is the field's type properly encoded, and the second one the field's name. Fields are protected
and non-final
by default. The returned FieldHandler
can be use to change these flags using setFlags(flags : U2)
which assigns the access_flags field.
To add a method, use:
val mh: MethodHandler = classFile.addMethod("I", "sayHello", "IZLjava/lang/String;")
This adds a method named ''sayHello'', which returns an integer and takes as arguments an integer, a Boolean and a string. An accepted alternative syntax is the following:
val mh: MethodHandler = classFile.addMethod("I", "sayHello", "I", "Z", "Ljava/lang/String;")
...where the parameter types are explicitely separated.
Methods are public
by default. The main usage of the MethodHandler
is to get a CodeHandler
to attach code to the method (see below).
To attach code to a method, one needs to recover a CodeHandler
instance referring to that method. The way to do this is the following:
val ch: CodeHandler = methodHandler.codeHandler
Alternatively, the handler can be recovered directly when the method is added as following:
val ch: CodeHandler = classFile.addMethod("V", "foo", "I").codeHandler
A CodeHandler
instance can be used to add byte codes to a method body using a "C++ stream"-like notation. All Java byte codes are supported. The following is the code for a non-static method that multiplies its first (integer) argument by two and returns the result:
ch << ILOAD_1 << DUP << IADD << IRETURN
Java byte codes are written using exactly the names used in the JVM specification, except that they are fully-capitalized as in the example above.
Additionally to "standard" Java byte codes, Cafebabe defines some "abstract" byte codes which are really just here for convenience purposes (most often to avoid having to explicitly deal with constant pool entries or to handle labels and jumps automatically). The convention is that they are capitalized using CamelCase to differentiate them from standard byte codes.
You can find a list of the available abstract byte codes here.
In order to get slot indices which are known not to be used at a given program point, we use the following method:
val fresh: Int = ch.getFreshVar
Note that all slots should be acquire through this method, as this is the only way to guarantee that the computation of the maximum number of locals will be accurate.
To get fresh label names for flow-control abstract bytecodes, the following helper is available:
ch.getFreshLabel("afterloop")
The label name is guaranteed to be fresh and will be prefix with the string.
When the body of a method is completely specified, one needs to call the .freeze
method on its CodeHandler
:
ch.freeze
This:
- translates all abstract byte codes into actual byte codes
- computes the jump offsets and introduce them at the right places
- computes the maximum stack height
- computes the maximum number of local variables
The last two numbers are required by the classfile format.
A default constructor can be automatically generated for a class file by calling:
classFile.addDefaultConstructor
A MethodHandler
for a public, static method returning void, called main
and with a string array as a single argument can be obtained by using:
val mainHandler = classFile.addMainMethod
...the code can then be attached using its CodeHandler
as usual.
As long as the code is not "frozen", it can be printed out by calling:
codeHandler.print
This is used mostly for debugging jumps and labels, as method and field accesses are already encoded with constant pool indices at this point (displayed as RawBytes(...)
).
Additional comments can be added to the stream of bytecodes using:
codeHandler << Comment("The loop starts here")
There comments are displayed in the debug print-out, but do not translate into any bytecode.
Cafebabe was written at EPFL by Philippe Suter and Sebastian Gfeller.
Let us know on GitHub if you find a bug.