Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 382
Tareq Sha Avatar asked Mar 03 '16 23:03

Tareq Sha


People also ask

How do I access private inner classes?

Accessing the Private Members Write an inner class in it, return the private members from a method within the inner class, say, getValue(), and finally from another class (from which you want to access the private members) call the getValue() method of the inner class.

Can Java classes access inner class private members?

A nested class is a member of its enclosing class. Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private.

How do you access private inner class with reflection?

Yes, you can instantiate a private inner class with Java reflection. To do that, you need to have an instance of outer class and invoke the inner class constructor which will use outer class instance in its first argument. @popgalop Inner classes are the same as methods.

How do I access an inner class from another class?

They are accessed using the enclosing class name. To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax: OuterClass.


1 Answers

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() {}
    }
}
like image 159
Holger Avatar answered Oct 20 '22 18:10

Holger