jeo-maven-plugin is a Maven plugin dedicated to disassembling Java bytecode. The process involves translating the Java bytecode into the EO programming language. The plugin also provides the ability to assemble EO back into Java bytecode.
You need at least Maven 3.1+ and Java 11+ to run the plugin. (Actually, the plugin requires Java 8+, but since the main dependency eo requires Java 11, we are obligated to use it as well.)
The plugin can convert compiled .class files into EO by using
the disassemble goal.
The assemble goal can convert EO back into bytecode.
The default phase for the plugin
is process-classes.
To optimize Java bytecode, you need to use both goals in the following order:
disassemblecreates EO files in thetarget/generated-sourcesdirectory.- Apply your optimizations to the EO files
in the
target/generated-sourcesdirectory. assemblescans thetarget/generated-sourcesdirectory for EO files and converts them back to Java bytecode.
More details about plugin usage can be found in our Maven site.
You can run the plugin directly from the command line using the following commands:
mvn jeo:disassembleor
mvn jeo:assembleYou can run the plugin from the Maven lifecycle by adding the following
configuration to your pom.xml file:
<build>
<plugins>
<plugin>
<groupId>org.eolang</groupId>
<artifactId>jeo-maven-plugin</artifactId>
<version>0.14.17</version>
<executions>
<execution>
<id>bytecode-to-eo</id>
<phase>process-classes</phase>
<goals>
<goal>disassemble</goal>
</goals>
</execution>
<execution>
<id>eo-to-bytecode</id>
<phase>process-classes</phase>
<goals>
<goal>assemble</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>In order to exclude debug information in the generated EO files, you can set
the short option.
<configuration>
<mode>short</mode>
</configuration>This option will exclude line numbers and local variable names,
together with their corresponding labels. The default mode is debug.
Each time the plugin converts EO back to bytecode, it verifies it. If the
verification fails, the build also fails. You can disable this verification by
setting the skipVerification parameter to true:
<configuration>
<skipVerification>true</skipVerification>
</configuration>At times, it might be beneficial to generate intentionally flawed bytecode.
After generating XMIR or before the assemble goal,
you might need to check its correctness.
We do it by using objectionary/lints
repository.
By default, the plugin does not run lints.
To enable them, you need to set xmirVerification to true:
<configuration>
<xmirVerification>true</xmirVerification>
</configuration>You can extend the logging output of the plugin by setting the debug parameter
to true:
<configuration>
<debug>true</debug>
</configuration>This will enable more detailed logging output, which can be useful for debugging purposes.
Without the debug parameter:
1/3 WithoutPackage.xmir (9Kb) disassembled in 1sWith the debug parameter:
1/3 .../WithoutPackage.class disassembled to .../WithoutPackage.xmir (9Kb) in 1sYou can run the plugin in a project that does not contain a pom.xml file.
For example, to disassemble compiled classes, use:
mvn org.eolang:jeo-maven-plugin:0.14.17:disassemble \
-Djeo.disassemble.sourcesDir=input \
-Djeo.disassemble.outputDir=xmirThe full list of available parameters is documented in DisassembleMojo.java.
Similarly, you can run the assemble goal:
mvn org.eolang:jeo-maven-plugin:0.14.17:assemble \
-Djeo.assemble.sourcesDir=xmir \
-Djeo.assemble.outputDir=output(Again, the full list of available parameters is documented
in AssembleMojo.java.)
Note that the parameters for assembling use the jeo.assemble prefix,
while the parameters for disassembling use the jeo.disassemble prefix.
jeo-maven-plugin relies on the notation
when producing XMIR files and parsing them back into bytecode.
The plugin can transform Java bytecode into EO and back. Usually, the plugin
transforms each bytecode class file into a separate EO file, maintaining a
one-to-one relationship. If the Java class has the name Application.class, the
EO
file will have the name Application.xmir.
For example, consider the following Java class:
package org.eolang.jeo;
public class Application {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}with the following bytecode representation:
{
public org.eolang.jeo.Application();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/eolang/jeo/Application;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String Hello, World!
5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
After running the jeo:disassemble goal, the plugin will generate the
following EO:
+package j$org.j$eolang.j$jeo
jeo.class > j$Application
jeo.int > version
00-00-00-00-00-00-00-34
jeo.int > access
00-00-00-00-00-00-00-21
"java/lang/Object" > supername
jeo.seq.of0 > interfaces-696199744
jeo.method > j$object@init@-%28%29V
jeo.int
00-00-00-00-00-00-00-01
"()V"
""
jeo.seq.of0 > aca3ae21-f727-41cc-b8df-791e7031904e-1635871459
jeo.maxs
jeo.int
00-00-00-00-00-00-00-01
jeo.int
00-00-00-00-00-00-00-01
jeo.params
jeo.seq.of0 > annotations-1586121642
jeo.seq.of3 > body-1998918745
jeo.opcode.aload
jeo.int
00-00-00-00-00-00-00-19
jeo.int
00-00-00-00-00-00-00-00
jeo.opcode.invokespecial
jeo.int
00-00-00-00-00-00-00-B7
"java/lang/Object"
"<init>"
"()V"
jeo.bool
00-
jeo.opcode.return
jeo.int
00-00-00-00-00-00-00-B1
jeo.seq.of0 > trycatchblocks-object@init@-1849817803
jeo.seq.of0 > local-variable-table-1794934203
jeo.method > j$main-%28%5BLjava%2Flang%2FString%3B%29V
jeo.int
00-00-00-00-00-00-00-09
"([Ljava/lang/String;)V"
""
jeo.seq.of0 > f6c6c536-0b96-4357-bbba-d2966968b266-658978372
jeo.maxs
jeo.int
00-00-00-00-00-00-00-02
jeo.int
00-00-00-00-00-00-00-01
jeo.params
jeo.param > param-%5BLjava%2Flang%2FString%3B-arg0-0-0-1064778491
jeo.seq.of0 > param-annotations-0-666850426
jeo.seq.of0 > annotations-639467587
jeo.seq.of4 > body-1023152593
jeo.opcode.getstatic
jeo.int
00-00-00-00-00-00-00-B2
"java/lang/System"
"out"
"Ljava/io/PrintStream;"
jeo.opcode.ldc
jeo.int
00-00-00-00-00-00-00-12
"Hello, World!"
jeo.opcode.invokevirtual
jeo.int
00-00-00-00-00-00-00-B6
"java/io/PrintStream"
"println"
"(Ljava/lang/String;)V"
jeo.bool
00-
jeo.opcode.return
jeo.int
00-00-00-00-00-00-00-B1
jeo.seq.of0 > trycatchblocks-main-727934521
jeo.seq.of0 > local-variable-table-2134319645
jeo.seq.of0 > annotations-785801830
jeo.seq.of0 > attributes-744976367
As you can see, there are many EO objects that represent the Java bytecode
primitives, like jeo.opcode, jeo.int, jeo.method, etc.
During disassembly, the jeo-maven-plugin creates a set of objects
representing bytecode primitives.
These objects provide a way to handle various aspects of Java bytecode.
Below is the full list of these objects, grouped by category:
jeo.opcode.*Represents a single bytecode instruction likeaload_0,iconst_0, etc.
jeo.classRepresents a Java class.jeo.methodRepresents a Java method.jeo.fieldRepresents a Java field.jeo.paramsRepresents method parameters.jeo.paramRepresents a single method parameter.jeo.maxsRepresents the maximum stack and local variable sizes.
jeo.int- Represents an integer value.jeo.bool- Represents a boolean value.jeo.string- Represents a string value.jeo.float- Represents a float value.jeo.double- Represents a double value.jeo.long- Represents a long value.jeo.char- Represents a char value.jeo.short- Represents a short value.jeo.byte- Represents a byte value.jeo.bytes- Represents a byte array.
jeo.nullableRepresents an object that can benull.jeo.arrayRepresents an array of objects.jeo.typeRepresents a Java type.jeo.seq.*Represents a sequence of objects with a specific size, likejeo.seq.of0,jeo.seq.of1, etc.
jeo.annotationRepresents a Java annotation.jeo.annotation-propertyRepresents a single annotation element.jeo.annotation-default-valueRepresents a default value of a Java interface method or annotation property.jeo.inner-classRepresents a Java inner class annotation property.
jeo.local-variableRepresents a local variable entry.jeo.try-catchRepresents a try-catch block.
jeo.labelRepresents a Java label.jeo.handleRepresents a Java method handle.jeo.frameRepresents a stack frame.
To build the plugin from the source code, you need to clone the repository and run the following command:
mvn clean install -Pqulice,longPay attention to the qulice profile, which activates the static analysis
tools. The long profile is optional and runs the full test suite, including
long-running integration tests.
To run the benchmarks, you need to execute the following command:
mvn clean verify -PbenchmarkBefore running the benchmarks, make sure you have the .env file in the root
directory of the project. The file should contain the following environment
variables:
PROFILER=/path/to/async-profiler/profiler.shFork the repository, make changes, then send us
a pull request.
We will review your changes and apply them to the master branch shortly,
provided they don't violate our quality standards. To avoid frustration,
before sending us your pull request, please run the full Maven build:
mvn clean install -PquliceAlso, if you want to check the generated XMIR, you can run:
mvn clean install -PverifyThis command will check all generated XMIR using objectionary/lints. It might take significantly more time to build, but it will ensure that all transformations are correct and aligned with the XMIR specification.
You will need Maven 3.3+ and Java 11+ installed.