Access to private inner classes in java ASM

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

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 interaction example is inheritance - I'm trying to use the OtherChild class, which extends the private inner class of Child. I get this error message when the class loader validates 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 being done from the "safe zone" where a closed inner class is available.

Thank you

+7
source share
2 answers

The rule that inner and outer classes can access their private members is a pure Java programming language construct that does not affect JVM access checks. When inner classes were introduced in Java 1.1, they were introduced in such a way that they did not require changes in the JVM. From the point of view of the JVM, nested classes are ordinary (upper) classes with some additional, uninformed meta-information.

When the inner class is declared private , its usual level of access to the class is "default" aka package-private. When protected declared, it will be public at the JVM level.

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

So, from the perspective of the JVM, you are trying to subclass a package-private class, and the dollar in the name is just an ordinary character in the name. The generated class has an appropriate qualified name, but you are trying to define it in another class loader, so the JVM considers these packages to be not identical at run time, despite their identical name.

You can check if access to the package level works if you define a class inside 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 can declare Child as protected . Starting with its low-level public class, it will be available to other class loaders.

Note that in both cases, you created a new inner class, but only a class called Parent$OtherChild that extends the inner class. The only difference is meta-information about the relationship between outer and inner classes, but if you add this attribute to your generated class, claiming that it is an inner Parent class, it may happen that it is rejected by the verifier because the meta-information from Parent does not mention the existence inner class of OtherChild . This is the only place the JVM can ever look at this attribute.

But, in addition to the relationship between inner classes of Reflection, in any case there is no functional difference between top-level classes and nested classes. As already mentioned, classes actually do not have protected and private access levels, and for all other access users you still need to generate the necessary code. If you cannot change the code of existing Parent or Parent$Child classes, you cannot access those of your private members for which these synthetic access methods do not yet exist ...


Starting with Java 9, there is a standard way to define a new class in an accessible context, which makes the "Reflection with access override" approach shown above obsolete for this use case, for example. 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() {} } } 
+3
source

I am changing a private inner class to a public inner class and I have no problem 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(); } 
0
source

All Articles