0

I am writing some code to check the structure of other people's code.

Lets say ClassB extends ClassA

I know I can do

Class clazz = Class.forName("classB");
clazz.getSuperclass();
clazz.getName(); //ClassA

but I dont want to do it this way because it requires my jvm return the actual class of ClassA (which is not in the classpath)

currently I am using

Class clazz = Class.forName("classB", false, classLoader);

to not initialize the class, so that I dont have to put so many irrelevant jars into my classpath.

Is there a way I can get the Super's classname from ClassB without requiring me to include ClassA's jar in the classpath?

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Why not `Class.forName("classB", false, classLoader).getSuperclass()`? – Sweeper Mar 31 '21 at 03:21
  • 4
    If `ClassA` isn't on the classpath, you won't be able to load `ClassB extends ClassA`. – chrylis -cautiouslyoptimistic- Mar 31 '21 at 03:21
  • 4
    If `ClassA` is not in the classpath, then you need to read the `ClassB.class` file directly, so go **find a bytecode library** and read the file, to learn what superclass the file specifies. I mean, you could also try reading the file yourself, but why do that when there are free libraries available that knows how to read the file? – Andreas Mar 31 '21 at 03:37
  • 1
    @chrylis-cautiouslyoptimistic- you can still do Class clazz = Class.forName("classB", false, classLoader); because you dont initialize the class, an example would be like looking at the .java file. – user3242477 Mar 31 '21 at 04:11

1 Answers1

2

There’s a fundamental misconception within

currently I am using

Class clazz = Class.forName("classB", false, classLoader);

to not initialize the class, so that I dont have to put so many irrelevant jars into my classpath.

The initialize parameter specifies whether the static initializer should run, i.e. static {} blocks and non-constant initializer expressions of static fields. It does not prevent the loading of the superclass. That’s simply impossible.

It’s also strange to consider the superclass or its containing jar file “irrelevant”. The superclass is rather a crucial aspect of a class.

The JVM specification says:

5.3.5. Deriving a Class from a class File Representation

The following steps are used to derive a Class object for the nonarray class or interface C denoted by N using loader L from a purported representation in class file format.

  1. If C has a direct superclass, the symbolic reference from C to its direct superclass is resolved using the algorithm of §5.4.3.1. Note that if C is an interface it must have Object as its direct superclass, which must already have been loaded. Only Object has no direct superclass.

    Any exceptions that can be thrown due to class or interface resolution can be thrown as a result of this phase of loading. In addition, this phase of loading must detect the following errors:

    • If any of the superclasses of C is C itself, loading throws a ClassCircularityError.

    • Otherwise, if the class or interface named as the direct superclass of C is in fact an interface or a final class, loading throws an IncompatibleClassChangeError.

    • Otherwise, if C is a class and some instance method declared in C can override (§5.4.5) a final instance method declared in a superclass of C, loading throws an IncompatibleClassChangeError.

It’s impossible to perform these mandatory checks without loading the superclass.

In principle, it’s possible to read a class’ superclass declaration without loading the class itself, which also prevents all kind of linkage problems, by reading and parsing the class file. It only needs a tiny change to make the code of this answer read the superclass name instead of the class name.

It still doesn’t change the fact that you can’t use a class without its superclass, so its usefulness is limited.

static String getSuperClassNameOf(ClassLoader loader, String className)
    throws ClassNotFoundException, IOException {

    String resName = className.replace('.', '/') + ".class";
    InputStream is;
    getStream: {
        for(; loader != null; loader = loader.getParent()) {
            is = loader.getResourceAsStream(resName);
            if(is != null) break getStream;
        }
        is = ClassLoader.getSystemResourceAsStream(resName);
    }
    if(is == null) throw new ClassNotFoundException(className);
    className = getSuperClassName(ByteBuffer.wrap(is.readAllBytes()));
    return className.replace('/', '.');
}

static String getSuperClassName(ByteBuffer buf) {
    if(buf.order(ByteOrder.BIG_ENDIAN).getInt()!=0xCAFEBABE) {
        throw new IllegalArgumentException("not a valid class file");
    }
    int minor=buf.getChar(), ver=buf.getChar(), poolSize=buf.getChar();
    int[] pool = new int[poolSize];
    //System.out.println("version "+ver+'.'+minor);
    for(int ix=1; ix<poolSize; ix++) {
        String s; int index1=-1, index2=-1;
        byte tag = buf.get();
        switch(tag) {
            default: throw new UnsupportedOperationException(
                    "unknown pool item type "+buf.get(buf.position()-1));
            case CONSTANT_Utf8:
                buf.position((pool[ix]=buf.position())+buf.getChar()+2); continue;
            case CONSTANT_Module: case CONSTANT_Package: case CONSTANT_Class:
            case CONSTANT_String: case CONSTANT_MethodType:
                pool[ix]=buf.getChar(); break;
            case CONSTANT_FieldRef: case CONSTANT_MethodRef:
            case CONSTANT_InterfaceMethodRef: case CONSTANT_NameAndType:
            case CONSTANT_InvokeDynamic: case CONSTANT_Dynamic:
            case CONSTANT_Integer: case CONSTANT_Float:
                buf.position(buf.position()+4); break;
            case CONSTANT_Double: case CONSTANT_Long:
                buf.position(buf.position()+8); ix++; break;
            case CONSTANT_MethodHandle: buf.position(buf.position()+3); break;
        }
    }
    int access = buf.getChar(), thisClass = buf.getChar(), superClass = buf.getChar();
    buf.position(pool[pool[superClass]]);
    return decodeString(buf);
}
private static String decodeString(ByteBuffer buf) {
    int size=buf.getChar(), oldLimit=buf.limit();
    buf.limit(buf.position()+size);
    StringBuilder sb=new StringBuilder(size+(size>>1));
    while(buf.hasRemaining()) {
        byte b=buf.get();
        if(b>0) sb.append((char)b);
        else {
            int b2 = buf.get();
            if((b&0xf0)!=0xe0)
                sb.append((char)((b&0x1F)<<6 | b2&0x3F));
            else {
                int b3 = buf.get();
                sb.append((char)((b&0x0F)<<12 | (b2&0x3F)<<6 | b3&0x3F));
            }
        }
    }
    buf.limit(oldLimit);
    return sb.toString();
}
private static final byte CONSTANT_Utf8 = 1, CONSTANT_Integer = 3,
    CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6,
    CONSTANT_Class = 7, CONSTANT_String = 8, CONSTANT_FieldRef = 9,
    CONSTANT_MethodRef = 10, CONSTANT_InterfaceMethodRef = 11,
    CONSTANT_NameAndType = 12, CONSTANT_MethodHandle = 15,
    CONSTANT_MethodType = 16, CONSTANT_Dynamic = 17, CONSTANT_InvokeDynamic = 18,
    CONSTANT_Module = 19, CONSTANT_Package = 20;

See also §4. The class File Format.

Holger
  • 285,553
  • 42
  • 434
  • 765