[code-reflection] RFR: Add sample annotation processor using code models

Maurizio Cimadamore mcimadamore at openjdk.org
Wed Sep 10 11:13:04 UTC 2025


On Mon, 8 Sep 2025 13:49:50 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

> This change adds a new sample of an annotation processor that uses code models to:
> * reject unsupported methods (e.g. `System.gc`, `System.exit`)
> * reject unsupported language construct (e.g. `try`, `throw`)
> 
> The annotation processor is somewhat configurable, and can be used by developers as an initial sketch upon which to build more custom compile-time checks.
> 
> When using the annotation processor to compile this:
> 
> 
> class Foo {
>    @CodeReflection
>    void test1() {
>       System.exit(1);
>    }
> 
>     @CodeReflection
>     void test2() {
>         System.gc();
>     }
> 
>     @CodeReflection
>     void test3() {
> 
>        try {
>            test2();
>        } finally {
>            test3();
>        }
> 
>     }
> }
> 
> 
> The following error messages are generated:
> 
> 
> Foo.java:5: error: System.exit not supported in reflectable methods
>    void test1() {
>         ^
> Foo.java:10: error: System.gc not supported in reflectable methods
>     void test2() {
>          ^
> Foo.java:15: error: try/catch statement not supported in reflectable methods
>     void test3() {
>          ^
> 
> 
> When putting this together, I noted some issues, reported below:
> 
> * javac was failing to execute annotation processors that depended on `jdk.incubator.code` module
> * obtaining the model from a method that contains attribution errors crashed the annotation processor
> * some packages (e.g. the ones in java.base) are not exported to the dynamic module layer's `jdk.incubator.code`
> * it is not possible to report the message at a more accurate location because the `Messager` API does not allow for this
> 
> Many thanks to @lahodaj for the invaluable help when fighting with module layers :-)

cr-examples/samples/src/main/java/oracle/code/samples/CodeReflectionProcessor.java line 31:

> 29: @SupportedAnnotationTypes("jdk.incubator.code.CodeReflection")
> 30: @SupportedSourceVersion(SourceVersion.RELEASE_26)
> 31: public class CodeReflectionProcessor extends AbstractProcessor {

Once we agree this example is sufficient, I can add some documentation to describe what it does, and how to use it

src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java line 196:

> 194: 
> 195:     protected ClassLoader getClassLoader(URL[] urls) {
> 196:         ClassLoader thisClassLoader = CodeReflectionSupport.CODE_LAYER != null ?

We need to use a "base" classloader which is the same as the one used to build the `jdk.incubator.code` module layer (if it exists), otherwise we won't be able to load types defined there.

src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java line 1174:

> 1172:             ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
> 1173:             return ServiceLoader.load(layer, service);
> 1174:         } else if (CodeReflectionSupport.CODE_LAYER != null) {

In some cases javac uses the service loader API to load processors and plugins directly -- again, we need to make sure the `jdk.incubator.code` layer is used if present.

src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java line 1766:

> 1764:                 // But we need to do so by calling a method in java.base reflectively
> 1765:                 try {
> 1766:                     Class<?> codeModuleLayerInit = Class.forName("jdk.internal.access.code.CodeModuleLayerInit");

If the babylon module is not in the module graph when running javac, then we need to explicitly export all java.base packages to that module, to make sure it runs correctly (this might be too broad).

src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java line 597:

> 595:                         try {
> 596:                             return reflectMethods.getMethodBody(enclosingClass, methodTree, attribBlock, make);
> 597:                         } catch (Throwable ex) {

This avoids crashing if `reflectMethods` throws (which can happen if the source code we come from contains type checking errors -- such as calling methods with wrong arity)

-------------

PR Review Comment: https://git.openjdk.org/babylon/pull/554#discussion_r2330348286
PR Review Comment: https://git.openjdk.org/babylon/pull/554#discussion_r2330332719
PR Review Comment: https://git.openjdk.org/babylon/pull/554#discussion_r2330335256
PR Review Comment: https://git.openjdk.org/babylon/pull/554#discussion_r2330804373
PR Review Comment: https://git.openjdk.org/babylon/pull/554#discussion_r2330338305


More information about the babylon-dev mailing list