Search code examples
javajvmbytecode-manipulation

Can an anonymous .class that extends a class (such as an enum) be hacked in such a way as to implement an interface?


I have an interface such as:

public interface Foo() {
    public void bar();
}

And I want to make an anonymous enum that implements it, as if this was valid Java:

public enum MyEnum {
    A implements Foo {
        public void bar() {
            System.out.println("Hello!");
        }
    },
    B,
    C;
}

(Notice how only A implements Foo, but not B or C.)


(This is related to those "What can do you in Java bytecode that you can't do in Java?" questions.)


Solution

  • It is indeed possible to create such class files via byte-code manipulation. Here is an example program which does this using the ASM library:

    import java.io.IOException;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import org.objectweb.asm.*;
    
    public class EnumHack {
        interface Foo {
            public void bar();
        }
        enum MyEnum {
            A {
                public void bar() {
                    System.out.println("Hello!");
                }
            },
            B,
            C;
        }
        public static void main(String... arg) {
            try {
                patch();
            } catch (IOException|URISyntaxException ex) {
                System.err.println("patching failed: "+ex);
                return;
            }
            test();
        }
        static void test() {
            for(MyEnum e: MyEnum.values()) {
                System.out.println(e.name());
                if (e instanceof Foo) {
                    System.out.println("\timplements Foo");
                    ((Foo)e).bar();
                }
            }
        }
        static void patch() throws IOException, URISyntaxException {
            URL url = MyEnum.class.getResource("EnumHack$MyEnum$1.class");
            ClassReader cr=new ClassReader(url.openStream());
            ClassWriter cw=new ClassWriter(cr, 0);
            cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
                @Override
                public void visit(int version, int access, String name,
                        String signature, String superName, String[] interfaces) {
                    if(interfaces.length==0) // not patched yet
                        interfaces=new String[] { Foo.class.getName() };
                    super.visit(version, access, name, signature, superName, interfaces);
                }
            }, 0);
            Files.write(Paths.get(url.toURI()), cw.toByteArray());
        }
    }
    

    It prints

    A
        implements Foo
    Hello!
    B
    C
    

    When running under Oracles’s Java 8 with the class files residing in the file system. When running under different JVMs or bundled in a Jar file writing back the modified class file may fail or the enum might have been loaded eagerly and not reflect the modification (but it would on the next run then).

    But it shows that generally, that byte-code manipulation is possible and when performed before starting the application, it will work even in the more complicated scenarios.


    Still, I see this only as an interesting fun hack, but nothing you should build applications on. There is no problem this hack will solve that you couldn’t solve using a delegation object which implements the interface…