Java agent to unfinalize class

That one boring Saturday I wanted to learn something more about agents and thought it would be a really cool idea to “unfinalize” java.lang.String.

I started working on the project, developed simple transformer:

if (name.equals("java/lang/String")) {

    ClassPool classPool = ClassPool.getDefault();
    final CtClass ctClass;
    try {
        ctClass = classPool.get(name.replace("/", "."));
    } catch (NotFoundException e) {
        throw new RuntimeException(e);
    }
    int modifiers = ctClass.getModifiers();
    if (Modifier.isFinal(modifiers)) {

        System.out.println(name + " modifiers: " + modifiers);
        ctClass.setModifiers(ctClass.getModifiers() & -Modifier.FINAL);

        try {
            System.out.println("removed modifiers");
            return ctClass.toBytecode();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}
return b;

and it quickly turned out that java.lang package isn’t passed to ClassFileTransformer used in Java agents. When I scan for java.lang.String, it doesn’t appear, so I won’t be able to modify it. I found another victim that’s a final class java/nio/channels/Channels, so I’ll go with it for now.

I got the agent working, so I developed a victim Main class where I can test it with java -javaagent. The class only prints Class modifiers, so I can test that the agent changes binary values:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("java/nio/channels/Channels".replace("/", "."));
        final int modifiers = clazz.getModifiers();
        System.out.println("Happy days for modifiers: " + modifiers);
    }
}

Now I can package the agent and run Main class using:

mvn package && \
java -javaagent:./target/java-agent-to-remove-class-modifiers-1.0-SNAPSHOT.jar Main.java

Which results with the following output:

java/nio/channels/Channels modifiers: 17
remove modifiers
Exception in thread "main" java.lang.IllegalAccessError: failed to access class java.nio.channels.Channels from class java.nio.file.spi.FileSystemProvider (java.nio.channels.Channels and java.nio.file.spi.FileSystemProvider are in module java.base of loader 'bootstrap')
        at java.base/java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:426)
        at java.base/java.nio.file.Files.newInputStream(Files.java:160)
        at jdk.compiler/com.sun.tools.javac.launcher.Main.readFile(Main.java:235)
        at jdk.compiler/com.sun.tools.javac.launcher.Main.compile(Main.java:371)
        at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:189)
        at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)

Turns out, final flag cannot be removed from a class in Java 17, neither in 11, but when I split the run instruction to Java 8 compatible format (no single runnable classes):

sdk use java 8.0.265-open
javac Main.java
java -javaagent:./target/java-agent-to-remove-class-modifiers-1.0-SNAPSHOT.jar Main
java/nio/channels/Channels modifiers: 17
remove modifiers
Happy days for modifiers: 16

It was possible to modify the final flag of a class in Java 8 :)