简体   繁体   中英

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

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:

  1. inspect the class file using JClassLib, and compare the access flags with the def from the JVM Spec
  2. use the reflection API
  3. 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();

}
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM