├── .gitignore
├── README.md
├── dev.thihup.bytecode.annotation.core
├── pom.xml
└── src
│ └── main
│ └── java
│ ├── dev
│ └── thihup
│ │ └── bytecode
│ │ └── annotation
│ │ └── core
│ │ └── Bytecode.java
│ └── module-info.java
├── dev.thihup.bytecode.annotation.processor
├── pom.xml
└── src
│ └── main
│ ├── java
│ ├── dev
│ │ └── thihup
│ │ │ └── bytecode
│ │ │ └── annotation
│ │ │ └── processor
│ │ │ └── BytecodeProcessor.java
│ └── module-info.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.annotation.processing.Processor
├── dev.thihup.bytecode.annotation.tests
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ ├── dev
│ │ └── thihup
│ │ │ └── bytecode
│ │ │ └── annotation
│ │ │ └── examples
│ │ │ ├── CondyBSMExample.java
│ │ │ ├── CondyConstantBootstrapsExample.java
│ │ │ ├── HelloWorld.java
│ │ │ ├── IndyAddInts.java
│ │ │ └── IndySimple.java
│ │ └── module-info.java
│ └── test
│ └── java
│ ├── dev
│ └── thihup
│ │ └── bytecode
│ │ └── annotation
│ │ └── tests
│ │ ├── ConstantDynamicTest.java
│ │ ├── HelloWorldTest.java
│ │ └── InvokeDynamicTest.java
│ └── module-info.java
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .factorypath
3 | .DS_Store
4 | .project
5 | .settings/
6 | .vscode
7 | dependency-reduced-pom.xml
8 | nb-configuration.xml
9 | nbactions.xml
10 | nbproject/
11 | target/
12 | .idea
13 | *.iml
14 | *.original~
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Bytecode annotation processor
2 | ----------------------
3 |
4 | This project allows the usage of inline bytecode in the Java programming language.
5 | It uses the [JASM syntax](https://wiki.openjdk.java.net/display/CodeTools/Appendix+A) for the instructions.
6 |
7 | One use case would be to use the InvokeDynamic instruction and/or the CONSTANT_Dynamic. Both
8 | features are not available in Java programming language.
9 |
10 | It is heavily inspired by the [Groovy Bytecode AST](https://github.com/melix/groovy-bytecode-ast/) project.
11 |
12 | However, this project uses only supported API.
13 |
14 | Example
15 | ------
16 | ```java
17 | public class HelloWorld {
18 |
19 | @Bytecode(
20 | value = """
21 | getstatic java/lang/System.out:"Ljava/io/PrintStream;";
22 | ldc "Hello, world!";
23 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V";
24 | return;
25 | """,
26 | className="dev.thihup.bytecode.annotation.examples.HelloWorldImpl"
27 | )
28 | void myMethod() {
29 | HelloWorldImpl.invoke();
30 | }
31 |
32 | }
33 | ```
34 | See the [tests](dev.thihup.bytecode.annotation.tests) folder for more examples.
35 |
36 | Limitations
37 | -----------
38 |
39 | - As we can only generate external classes, it is not possible to create an inner class to store the inline bytecode (the generated class is valid, but you won't be able to reference it in a Java source file);
40 | - For the same reason as above, it is not possible to access private field of the annotated class.
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | dev.thihup.bytecode.annotation.processor
9 | bytecode-annotation-processor-parent
10 | 1.0-SNAPSHOT
11 |
12 |
13 | bytecode-annotation-core
14 |
15 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.core/src/main/java/dev/thihup/bytecode/annotation/core/Bytecode.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.core;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Bytecode annotation
10 | *
11 | * It creates a new class with a method body specified in this annotation.
12 | */
13 | @Retention(RetentionPolicy.SOURCE)
14 | @Target(ElementType.METHOD)
15 | public @interface Bytecode {
16 |
17 | /**
18 | * The bytecode of the method utilizing the JASM syntax
19 | */
20 | String value();
21 |
22 | /**
23 | * The class name of the class that contains the method to be invoked.
24 | * It uses the fully qualified name of the class (using dots).
25 | */
26 | String className();
27 |
28 | /**
29 | * The signature of the method to be invoked.
30 | * Default to "()V"
31 | */
32 | String methodSignature() default "()V";
33 |
34 | /**
35 | * The name of the method to be invoked. Default to "invoke"
36 | */
37 | String methodName() default "invoke";
38 |
39 | /**
40 | * The class file version
41 | * Default to "61:0" (Java 17)
42 | */
43 | String classVersion() default "61:0";
44 |
45 | /**
46 | * If specified, it will use the of the jasmCode instead of creating a Jasm code
47 | * from the other parameters.
48 | */
49 | String jasmCode() default "";
50 | }
51 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.core/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module dev.thihup.bytecode.annotation.core {
2 | exports dev.thihup.bytecode.annotation.core;
3 | }
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.processor/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | dev.thihup.bytecode.annotation.processor
9 | bytecode-annotation-processor-parent
10 | 1.0-SNAPSHOT
11 |
12 |
13 | bytecode-annotation-processor
14 |
15 |
16 |
17 | dev.thihup.bytecode.annotation.processor
18 | bytecode-annotation-core
19 | ${project.version}
20 |
21 |
22 | org.openjdk.asmtools
23 | asmtools-core
24 | 7.0.b10-ea
25 |
26 |
27 |
28 |
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-compiler-plugin
33 |
34 | none
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.processor/src/main/java/dev/thihup/bytecode/annotation/processor/BytecodeProcessor.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.processor;
2 |
3 | import static java.util.stream.Collectors.toMap;
4 |
5 | import dev.thihup.bytecode.annotation.core.Bytecode;
6 | import java.io.IOException;
7 | import java.io.PrintWriter;
8 | import java.nio.file.FileVisitResult;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.nio.file.SimpleFileVisitor;
12 | import java.nio.file.attribute.BasicFileAttributes;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.Set;
16 | import javax.annotation.processing.AbstractProcessor;
17 | import javax.annotation.processing.Filer;
18 | import javax.annotation.processing.RoundEnvironment;
19 | import javax.annotation.processing.SupportedAnnotationTypes;
20 | import javax.annotation.processing.SupportedSourceVersion;
21 | import javax.lang.model.SourceVersion;
22 | import javax.lang.model.element.AnnotationMirror;
23 | import javax.lang.model.element.Element;
24 | import javax.lang.model.element.TypeElement;
25 | import javax.tools.JavaFileObject;
26 | import org.openjdk.asmtools.jasm.Main;
27 |
28 | @SupportedAnnotationTypes("dev.thihup.bytecode.annotation.core.Bytecode")
29 | @SupportedSourceVersion(SourceVersion.RELEASE_17)
30 | public final class BytecodeProcessor extends AbstractProcessor {
31 |
32 | @Override
33 | public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
34 | Set extends Element> elementsAnnotatedWith =
35 | roundEnv.getElementsAnnotatedWith(Bytecode.class);
36 | try {
37 | Path outputDirectory = Files.createTempDirectory("bytecodeProcessor");
38 | for (Element element : elementsAnnotatedWith) {
39 | processMethod(outputDirectory, element);
40 | }
41 |
42 | Files.walkFileTree(outputDirectory, new DeleteAllFilesVisitor());
43 | } catch (IOException e) {
44 | throw new RuntimeException(e);
45 | }
46 | return true;
47 | }
48 |
49 | private void processMethod(Path outputDirectory, Element element) throws IOException {
50 | Bytecode annotation = element.getAnnotation(Bytecode.class);
51 |
52 | String className = annotation.className();
53 | String classNameWithSlashes = className.replace('.', '/');
54 | String jasmCode = generateJasmCode(annotation, classNameWithSlashes);
55 |
56 | Path jasmFile = outputDirectory.resolve(className + ".jasm");
57 | Files.writeString(jasmFile, jasmCode);
58 |
59 | compile(outputDirectory, jasmFile);
60 |
61 | Filer filer = processingEnv.getFiler();
62 | JavaFileObject bytecode = filer.createClassFile(className, element);
63 | try (var outputStream = bytecode.openOutputStream()) {
64 | Path classFile = outputDirectory.resolve(classNameWithSlashes.concat(".class"));
65 | Files.copy(classFile, outputStream);
66 | }
67 | }
68 |
69 | private String generateJasmCode(Bytecode annotation, String classNameWithSlashes) {
70 | return !annotation.jasmCode().isEmpty() ? annotation.jasmCode() : """
71 | class %s
72 | version %s {
73 | static Method "%s":"%s" stack 100 locals 100 {
74 | %s
75 | }
76 | }
77 | """.formatted(classNameWithSlashes,
78 | annotation.classVersion(),
79 | annotation.methodName(), annotation.methodSignature(), annotation.value());
80 | }
81 |
82 | private void compile(Path outputDirectory, Path jasmFile) {
83 | Main compiler = new Main(new PrintWriter(System.out), "jasm") {
84 | {
85 | this.err = new PrintWriter(System.err);
86 | }
87 | };
88 | boolean compile = compiler.compile(
89 | new String[]{"-d", outputDirectory.toString(), jasmFile.toString()});
90 | if (!compile) {
91 | throw new RuntimeException("Compilation failed");
92 | }
93 | }
94 |
95 | private static class DeleteAllFilesVisitor extends SimpleFileVisitor {
96 |
97 | @Override
98 | public FileVisitResult postVisitDirectory(Path dir, IOException exc)
99 | throws IOException {
100 | Files.deleteIfExists(dir);
101 | return FileVisitResult.CONTINUE;
102 | }
103 |
104 | @Override
105 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
106 | throws IOException {
107 | Files.deleteIfExists(file);
108 | return FileVisitResult.CONTINUE;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.processor/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | import javax.annotation.processing.Processor;
2 |
3 | module dev.thihup.bytecode.annotation.processor {
4 | requires transitive dev.thihup.bytecode.annotation.core;
5 | requires java.compiler;
6 | requires asmtools.core;
7 | provides Processor with dev.thihup.bytecode.annotation.processor.BytecodeProcessor;
8 | }
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | dev.thihup.bytecode.annotation.processor.BytecodeProcessor
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | dev.thihup.bytecode.annotation.processor
9 | bytecode-annotation-processor-parent
10 | 1.0-SNAPSHOT
11 |
12 |
13 | bytecode-annotation-tests
14 |
15 |
16 |
17 | dev.thihup.bytecode.annotation.processor
18 | bytecode-annotation-core
19 | ${project.version}
20 | provided
21 |
22 |
23 |
24 |
25 |
26 |
27 | org.apache.maven.plugins
28 | maven-compiler-plugin
29 |
30 |
31 |
32 | dev.thihup.bytecode.annotation.processor
33 | bytecode-annotation-processor
34 | ${project.version}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/dev/thihup/bytecode/annotation/examples/CondyBSMExample.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.examples;
2 |
3 | import dev.thihup.bytecode.annotation.core.Bytecode;
4 | import java.lang.invoke.MethodHandles;
5 | import java.util.List;
6 |
7 | public class CondyBSMExample {
8 |
9 | @Bytecode(value =
10 | """
11 | ldc Dynamic REF_invokeStatic:dev/thihup/bytecode/annotation/examples/CondyBSMExample.fibonacci
12 | :"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/util/List;"
13 | :foo:"Ljava/util/List;";
14 | areturn;
15 | """,
16 | methodSignature = "()Ljava/util/List;",
17 | className="dev.thihup.bytecode.annotation.examples.Example2"
18 | )
19 | public List myMethod(int a, int b) {
20 | return Example2.invoke();
21 | }
22 |
23 | static List fibonacci(MethodHandles.Lookup lookup, String name, Class> methodType) throws Throwable {
24 | return List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/dev/thihup/bytecode/annotation/examples/CondyConstantBootstrapsExample.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.examples;
2 |
3 | import dev.thihup.bytecode.annotation.core.Bytecode;
4 |
5 | public class CondyConstantBootstrapsExample {
6 |
7 | @Bytecode(value =
8 | """
9 | ldc Dynamic REF_invokeStatic:java/lang/invoke/ConstantBootstraps.primitiveClass
10 | :"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Class;"
11 | :I:"Ljava/lang/Class;";
12 | areturn;
13 | """,
14 | methodSignature = "()Ljava/lang/Class;",
15 | className="dev.thihup.bytecode.annotation.examples.Example1"
16 | )
17 | public Class> myMethod() {
18 | return Example1.invoke();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/dev/thihup/bytecode/annotation/examples/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.examples;
2 |
3 | import dev.thihup.bytecode.annotation.core.Bytecode;
4 |
5 | public class HelloWorld {
6 |
7 | @Bytecode(
8 | value = """
9 | getstatic java/lang/System.out:"Ljava/io/PrintStream;";
10 | ldc "Hello, world!";
11 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V";
12 | return;
13 | """,
14 | className="dev.thihup.bytecode.annotation.examples.HelloWorldImpl"
15 | )
16 | public void myMethod() {
17 | HelloWorldImpl.invoke();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/dev/thihup/bytecode/annotation/examples/IndyAddInts.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.examples;
2 |
3 | import dev.thihup.bytecode.annotation.core.Bytecode;
4 | import java.lang.invoke.CallSite;
5 | import java.lang.invoke.ConstantCallSite;
6 | import java.lang.invoke.MethodHandle;
7 | import java.lang.invoke.MethodHandles;
8 | import java.lang.invoke.MethodType;
9 |
10 | public class IndyAddInts {
11 |
12 | @Bytecode(value =
13 | """
14 | iload_0;
15 | iload_1;
16 | invokedynamic REF_invokeStatic:
17 | dev/thihup/bytecode/annotation/examples/IndyAddInts.superAddBSM:
18 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;":
19 | "foo":"(II)I";
20 | ireturn;
21 | """,
22 | methodSignature = "(II)I",
23 | className="dev.thihup.bytecode.annotation.examples.AddInts"
24 | )
25 | public int myMethod(int a, int b) {
26 | return AddInts.invoke(a, b);
27 | }
28 |
29 | static CallSite superAddBSM(MethodHandles.Lookup lookup, String methodName, MethodType methodType) throws Throwable {
30 | MethodHandle addExact = lookup.findStatic(Math.class, "addExact",
31 | MethodType.methodType(int.class, int.class, int.class));
32 |
33 | return new ConstantCallSite(addExact.asType(methodType));
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/dev/thihup/bytecode/annotation/examples/IndySimple.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.examples;
2 |
3 | import dev.thihup.bytecode.annotation.core.Bytecode;
4 | import java.lang.invoke.CallSite;
5 | import java.lang.invoke.ConstantCallSite;
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.invoke.MethodType;
8 |
9 | public class IndySimple {
10 |
11 | @Bytecode(value =
12 | """
13 | invokedynamic REF_invokeStatic:
14 | dev/thihup/bytecode/annotation/examples/IndySimple.myBSMWithoutArgs:
15 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;":
16 | "foo":"()Ljava/lang/String;";
17 | areturn;
18 | """,
19 | methodSignature = "()Ljava/lang/String;",
20 | className="dev.thihup.bytecode.annotation.examples.IndySimpleWithoutArgs"
21 | )
22 | public String myMethod() {
23 | return IndySimpleWithoutArgs.invoke();
24 | }
25 |
26 | static CallSite myBSMWithoutArgs(MethodHandles.Lookup lookup, String methodName, MethodType methodType) throws Throwable {
27 | return new ConstantCallSite(MethodHandles.constant(String.class, "Hello World"));
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module dev.thihup.bytecode.annotation.examples {
2 | exports dev.thihup.bytecode.annotation.examples;
3 | requires dev.thihup.bytecode.annotation.core;
4 | }
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/test/java/dev/thihup/bytecode/annotation/tests/ConstantDynamicTest.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.tests;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import dev.thihup.bytecode.annotation.examples.CondyBSMExample;
6 | import dev.thihup.bytecode.annotation.examples.CondyConstantBootstrapsExample;
7 | import dev.thihup.bytecode.annotation.examples.IndyAddInts;
8 | import dev.thihup.bytecode.annotation.examples.IndySimple;
9 | import java.util.List;
10 | import org.junit.jupiter.api.Test;
11 |
12 | class ConstantDynamicTest {
13 |
14 | @Test
15 | void test() {
16 | assertEquals(int.class, new CondyConstantBootstrapsExample().myMethod());
17 | }
18 |
19 | @Test
20 | void test2() {
21 | List fibo = new CondyBSMExample().myMethod(5, 7);
22 | assertEquals(10, fibo.size());
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/test/java/dev/thihup/bytecode/annotation/tests/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.tests;
2 |
3 | import dev.thihup.bytecode.annotation.examples.HelloWorld;
4 | import org.junit.jupiter.api.Test;
5 |
6 | class HelloWorldTest {
7 |
8 | @Test
9 | void myMethod() {
10 | new HelloWorld().myMethod();
11 | }
12 | }
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/test/java/dev/thihup/bytecode/annotation/tests/InvokeDynamicTest.java:
--------------------------------------------------------------------------------
1 | package dev.thihup.bytecode.annotation.tests;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import dev.thihup.bytecode.annotation.examples.IndyAddInts;
6 | import dev.thihup.bytecode.annotation.examples.IndySimple;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class InvokeDynamicTest {
10 |
11 | @Test
12 | void test() {
13 | assertEquals("Hello World", new IndySimple().myMethod());
14 | }
15 |
16 | @Test
17 | void test2() {
18 | assertEquals(12, new IndyAddInts().myMethod(5, 7));
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/dev.thihup.bytecode.annotation.tests/src/test/java/module-info.java:
--------------------------------------------------------------------------------
1 | open module dev.thihup.bytecode.annotation.tests {
2 | requires dev.thihup.bytecode.annotation.examples;
3 | requires org.junit.jupiter.api;
4 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | dev.thihup.bytecode.annotation.processor
8 | bytecode-annotation-processor-parent
9 | 1.0-SNAPSHOT
10 | pom
11 |
12 |
13 | 17
14 | UTF-8
15 |
16 |
17 |
18 | dev.thihup.bytecode.annotation.core
19 | dev.thihup.bytecode.annotation.processor
20 | dev.thihup.bytecode.annotation.tests
21 |
22 |
23 |
24 |
25 |
26 | org.apache.maven.plugins
27 | maven-compiler-plugin
28 | 3.8.1
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-surefire-plugin
33 | 3.0.0-M5
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.junit.jupiter
41 | junit-jupiter-api
42 | 5.8.2
43 | test
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------