Access flag for private inner classes in Java - spec inconsistent with reflection API?

bytecode, java, jvm

Solution

First off, your first method isn't actually inspecting anything, it just displays a constant false. So the real question is why the second two methods give different results.

To see what's really going on, we can start by compiling the test class

public class TestModifiers {
// this is the class to be tested
private class InnerClass {}
}

Disassembling `TestModifiers$InnerClass.class` gives

.version 51 0
.source TestModifiers.java
.class super TestModifiers$InnerClass
.super java/lang/Object
.inner private InnerClass TestModifiers$InnerClass TestModifiers

.field synthetic final this$0 LTestModifiers;

.method private <init> : (LTestModifiers;)V
    ; method code size: 10 bytes
    .limit stack 2
    .limit locals 2
    aload_0
    aload_1
    putfield TestModifiers$InnerClass this$0 LTestModifiers;
    aload_0
    invokespecial java/lang/Object <init> ()V
    return
.end method

As you may notice, the classfile does not have the `private` flag set in the access flags (the only flag it has is `super`, which is set for all normal classes). This is not surprising, because ACC_PRIVATE is not actually a valid classfile access flag (JVMS8, page 71). Therefore, when you inspect the classfile access flags via ASM, you will naturally get the false result.

However, the class does have an `InnerClasses` attribute, and this attribute has the `private` among its access flags, because ACC_PRIVATE is a valid access flag for inner class attributes (JVMS8, page 116).

Could it be that java.lang.Class.getModifiers() is getting its data from the inner classes attribute? Well, it's a bit tricky to check. The method is a native method. Checking the source here shows that it calls JVM_GetClassModifiers. This is included in the header jvm.h, which contains an interesting comment.

/* Differs from JVM_GetClassModifiers in treatment of inner classes.
   This returns the access flags for the class as specified in the
   class file rather than searching the InnerClasses attribute (if
   present) to find the source-level access flags. Only the values of
   the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be
   valid. */
JNIEXPORT jint JNICALL
JVM_GetClassAccessFlags(JNIEnv *env, jclass cls);

So there you have it. JVM_GetClassModifiers actually does check the InnerClasses attribute, while JVM_GetClassAccessFlags checks only the classfile access flags, which is the equivalent of what you did with ASM.

Problem

I have problems understanding the use of access flags (in particular, private) for inner classes in Java. The flag I find in the byte code seems to be inconsistent with the information provided by the reflection API. I attach the following program to illustrate the problem. The program has a private inner class and analyses itself using three different methods: - inspect the class file using JClassLib, and compare the access flags with the def from the JVM Spec - use the reflection API - use ASM 5.0 Surprisingly, this gives different results, this is the output: ``` inner class is private (inspection): false inner class is private (reflection): true inner class is private (ASM): false ``` Does anybody know what is going on here? Code to replicate the problem follows, I have used a JRE build 1.8.0_05-b13 on a Mac to run this. ``` import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.lang.reflect.Modifier; public class TestModifiers { // this is the class to be tested private class InnerClass {} public static void main(String[] args) throws Exception { // check whether class is private using inspection, and comparison with standard int flags = 0x0020; // inspect class file using JClassLib int private_flag = 0x0002; // acc to http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1 System.out.println("inner class is private (inspection): " + ((flags & private_flag) == private_flag)); // check whether class is private using reflection Class inner = InnerClass.class; System.out.println("inner class is private (reflection): " + Modifier.isPrivate(inner.getModifiers())); // now try to do the same by reading byte code using ASM String PATH_TO_CLASSFILES = "<replace by path to class file>"; File classFile = new File(PATH_TO_CLASSFILES+"TestModifiers$InnerClass.class"); InputStream in = new FileInputStream(classFile); class Visitor extends ClassVisitor { public Visitor() { super(Opcodes.ASM5); } @Override public void visit(final int version, final int access, final String name,final String signature, final String superName,final String[] interfaces) { boolean isPrivate = ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE); System.out.println("inner class is private (ASM): " + isPrivate); } } new ClassReader(in).accept(new Visitor(), 0); in.close(); } } ```

Original source