Tareq Sha
Tareq Sha

Reputation: 516

Access private inner classes in java ASM

I have a class which contains several inner classes. I would like to generate additional inner classes that interact with the compile-time private inner classes using the ASM library. My code looks like:

public class Parent {

  public void generateClass() {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null,
             Type.getInternalName(Child.class), new String[]{});
    // .. generate the class
    byte[] bytes = cw.toByteArray();
    Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);
  }

  private static class Child {
  }

}

As shown, a simple example of interaction is inheritance - I am trying to gnerate class OtherChild that extends the private inner class Child. I get this error message while the class loader is verifying the class definition:

IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child

Is there a way to generate inner classes that can interact with other private inner classes? You can assume that this is performed from the "safe zone" where the private inner class is accessible.

thank you

Upvotes: 8

Views: 1940

Answers (2)

peacetrue
peacetrue

Reputation: 319

I change the private inner class to public inner class, and no problem to running your code.

@Test
    public void changeToPublic() throws Exception {
        String className = "com.github.asm.Parent$Child";
        ClassReader classReader = new ClassReader(className);
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM6, classWriter) {

            @Override
            public void visitInnerClass(String name, String outerName, String innerName, int access) {
                super.visitInnerClass(name, outerName, innerName, Modifier.PUBLIC);
            }

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                super.visit(version, Modifier.PUBLIC, name, signature, superName, interfaces);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                return super.visitMethod(Modifier.PUBLIC, name, descriptor, signature, exceptions);
            }
        };
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        ClassLoaderUtils.defineClass(getClass().getClassLoader(), className, bytes);
        new Parent().generateClass();
    }

Upvotes: -1

Holger
Holger

Reputation: 298103

The rule that inner and outer classes can access their private members is a pure Java programming language construct which isn’t reflected by the JVM’s access checks. When inner classes were introduced in Java 1.1, they were introduced in a way that didn’t require changes to the JVM. From the JVM’s point of view, nested classes are ordinary (top level) classes with some additional, ignorable meta information.

When an inner class is declared private, it’s ordinary class access level is “default” aka package-private. When it’s declared protected, it will be public on the JVM level.

When nested classes access each other’s private fields or methods, the compiler will generate synthetic helper methods with package-private access in the target class, providing the desired access.

So from the JVM’s point of view, you are trying to subclass a package-private class and the dollar in the name is just an ordinary name character. The generated class has a matching qualified name, but you are trying to define it in a different class loader, so the JVM considers these packages not identical at runtime, despite their identical name.

You can verify that the package level access works, if you define the class within the same class loader. Change the line

Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);

to

Method m=ClassLoader.class.getDeclaredMethod(
    "defineClass", String.class, byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> genClass=(Class<?>)m.invoke(
    Child.class.getClassLoader(), "Parent$OtherChild", bytes, 0, bytes.length);

Alternatively, you could declare Child as protected. Since it’s a public class on the low level then, it will be accessible by other class loaders.

Note that in both cases, you haven’t created a new inner class but just a class named Parent$OtherChild extending an inner class. The only difference is the meta information about the outer-inner class relationship but if you add that attribute to your generated class claiming that it was an inner class of Parent, it could happen that it gets rejected by the verifier because the meta information of Parent doesn’t mention the existence of an inner class OtherChild. That’s the only place where a JVM might ever look at this attribute.

But besides Reflection reporting inner class relationships, there is no functional difference between top level classes and nested classes anyway. As said, classes actually don’t have the access levels protected nor private and for all other member accesses, you’ll have to generate the necessary code yourself anyway. If you can’t modify the code of the existing classes Parent or Parent$Child, you can’t access those of their private members for which these synthetic accessor methods don’t already exist…


Starting with Java 9, there is a standard way to define a new class within an accessible context, which makes the “Reflection with access override” approach shown above obsolete for this use case, e.g. the following works:

public class Parent {
    public void generateClass() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        String superType = Type.getInternalName(Child.class);
        cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null, superType, null);
        MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superType, "<init>", "()V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
        // etc
        byte[] bytes = cw.toByteArray();
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            Class<?> genClass = lookup.defineClass(bytes);
            Child ch = (Child)
                lookup.findConstructor(genClass, MethodType.methodType(void.class))
                      .invoke();
            System.out.println(ch);
        } catch(Throwable ex) {
            Logger.getLogger(Parent.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    private static class Child {
        Child() {}
    }
}

Upvotes: 3

Related Questions