2

Is it possible to dig up a classes name from bytecode which is formed from the class' source code?

The situation is this: I get a classes bytecode remotely from somewhere, it doesn't matter where it comes from. To effectively load that class with a classloader i would need to have the class name as well... right?

JHollanti
  • 2,343
  • 8
  • 27
  • 39
  • Well, guess what the JVM itself is doing? Correct, it interprets the bytecode and gets the class name from it :) Without giving a little bit more detail on what, how and (mainly) why you are trying to do this giving an answer is difficult – sfussenegger Oct 30 '09 at 13:01
  • Yeah, yeah, noted... I would like to get the name of the class at runtime so i can dynamically load the class with a classloader, since you need to specify the name and classpath of the class you're loading. – JHollanti Oct 30 '09 at 13:10

6 Answers6

12

If you just need the class name, it's probably easier to parse the beginning of the class file yourself instead of adding a 3rd party library for class code manipulation just for this purpose. You just need the classes and strings from the constant pool, skip the access flags and then replace / with . in the class name. If you have a byte array, you can call this method with new ByteArrayInputStream(byteArray):

public static String getClassName(InputStream is) throws Exception {
    DataInputStream dis = new DataInputStream(is);
    dis.readLong(); // skip header and class version
    int cpcnt = (dis.readShort()&0xffff)-1;
    int[] classes = new int[cpcnt];
    String[] strings = new String[cpcnt];
    for(int i=0; i<cpcnt; i++) {
        int t = dis.read();
        if(t==7) classes[i] = dis.readShort()&0xffff;
        else if(t==1) strings[i] = dis.readUTF();
        else if(t==5 || t==6) { dis.readLong(); i++; }
        else if(t==8) dis.readShort();
        else dis.readInt();
    }
    dis.readShort(); // skip access flags
    return strings[classes[(dis.readShort()&0xffff)-1]-1].replace('/', '.');
}
Oak
  • 26,231
  • 8
  • 93
  • 152
jarnbjo
  • 33,923
  • 7
  • 70
  • 94
  • Neat. I hadn't looked at the details, but that's more succinct than I had expected. – McDowell Oct 30 '09 at 15:42
  • Yes, it's not particulary difficult. I sometimes wonder why people suggest to use 3rd party libraries (like in this case ASM) to solve rather simple problems, when the wrapup required the use the 3rd party library itself is almost more code than what it would require to solve the problem using a little brain and the standard Java API. Can't programmers program anymore and are they just able to copy, paste and glue libraries together to produce an almost-working, bloated result? – jarnbjo Oct 30 '09 at 16:50
  • 1
    Great, thanks! Sounds like a solid solution. I won't have a chance to try it out immediately, but i trust this works. "Can't programmers program anymore and are they just..." To a large extent, yes ;) Many of todays "coders" are not so much coders as they are integrators of libraries. Plus (especially with this task) to produce an error free solution would require a lot of work and study as i'm not at all familiar with Java bytecode. But thanks! – JHollanti Nov 01 '09 at 12:29
  • 4
    BE CAREFUL with the code given in this example. While it works for many class files, it throws an exception (ArrayIndexOutOfBoundsException) for others. This is actually a great example why sometimes using a well-tested library is better than doing it yourself. – typeracer Apr 19 '18 at 00:48
  • 2
    Typeracer is right. It looks like it fails for code with INVOKEDYNAMIC -- something with lambdas-related, or with boostrap entries inside constant pool – Valery Silaev Mar 03 '19 at 20:20
  • @typeracer This code was written almost 10 years ago and fails with newer class file versions, just as a library from back then would also fail. – jarnbjo Mar 03 '19 at 23:06
  • @jarnbjo I simply wanted to warn people that this solution no longer works. I wasn't trying to criticize your answer, which I concede, might have worked at the time you wrote it. – typeracer Mar 15 '19 at 06:06
  • 1
    I wanted to add to the comment of @typeracer that I was running exactly into this problem today. A (legacy) code base using exactly this piece of code to load class files and I am now failing with ArrayIndexOutOfBoundsException when using Functions in my class file. – tjeerdnet Apr 25 '19 at 14:15
  • Can you throw some light how you were able to resolve ArrayIndexOutOfBoundsException ? @typeracer – Anirudh Kashyap Jun 26 '19 at 20:21
  • @AnirudhKashyap I ended up using `org.apache.bcel.classfile.ClassParser` from [BCEL](https://commons.apache.org/proper/commons-bcel/) - it works well! – typeracer Aug 09 '19 at 09:58
  • I recommend `org.objectweb.asm.ClassReader` from https://asm.ow2.io/ (which is the base for many other popular byte code manipulating libraries/compilers - and even used in the OpenJDK... – dzim Dec 09 '20 at 11:17
  • 2
    @typeracer the main problem with this code is not that it is not up-to-date, but that it does neither, check the file’s version number nor report unknown tags, but rather treats everything unknown as if having four bytes to skip. [This answer](https://stackoverflow.com/a/52332101/2711488) happens to solve the same task, but is up-to-date and even better, would report unknown tags and is easier to update, due to named constants. – Holger Feb 09 '22 at 10:56
  • 2
    By the way, `DataInputStream` has a method `readUnsignedShort()` since day one, so there never was a need to repeatedly write `readShort()&0xffff`. – Holger Feb 09 '22 at 11:04
  • Note that the code above is not compatible with newer versions of Java, because there are new types in the constant pool, which 'Int' size. – Peter Verhas Oct 19 '22 at 16:08
2

The easiest way is probably using something like ASM:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.commons.EmptyVisitor;

public class PrintClassName {
  public static void main(String[] args) throws IOException {
    class ClassNamePrinter extends EmptyVisitor {
      @Override
      public void visit(int version, int access, String name, String signature,
          String superName, String[] interfaces) {
        System.out.println("Class name: " + name);
      }
    }

    InputStream binary = new FileInputStream(args[0]);
    try {
      ClassReader reader = new ClassReader(binary);
      reader.accept(new ClassNamePrinter(), 0);
    } finally {
      binary.close();
    }
  }
}

If you can't use a 3rd party library, you could read the class file format yourself.

McDowell
  • 107,573
  • 31
  • 204
  • 267
2

Just for completeness, in cases where the use of ASM5 library is acceptable, the following call could be used to obtain the class name from its byte representation.

public String readClassName(final byte[] typeAsByte) {
    return new ClassReader(typeAsByte).getClassName().replace("/", ".");
}
01es
  • 5,362
  • 1
  • 31
  • 40
1

You should be able to use javap to disassemble the byte code, if that just happens once in a while.

For doing it at runtime: use a byte-code manipulation library like Apache's BCEL (http://jakarta.apache.org/bcel) to analyse the byte code.

ankon
  • 4,128
  • 2
  • 26
  • 26
  • Is there no built-in functionality for this? – JHollanti Oct 30 '09 at 13:11
  • @JHollanti - what you want is backwards from the way things are usually done - you start with a name (often from a descriptor file) and then use path conventions to load the class bytes. There is no standard way to load and run multiple arbitrary classes (which one would be the entry point? etc.) – McDowell Oct 30 '09 at 13:30
0

I think you can use the ClassLoader.defineClass method, in a subclass of ClassLoader, to get the Class object for a given bytecode. (not tested)

user85421
  • 28,957
  • 10
  • 64
  • 87
0

Inspired by the solution provided by McDowell I created an updated version that works up to Java 19.

String getBinaryName(byte[] byteCode) {
        try (final var is = new DataInputStream(new ByteArrayInputStream(byteCode))) {
            final var magic = is.readInt();
            if (magic != 0xCAFEBABE) {
                throw new RuntimeException("Class file header is missing.");
            }
            
            final var minor = is.readUnsignedShort();
            final var major = is.readUnsignedShort();
            final int constantPoolCount = is.readShort();
            final var classes = new int[constantPoolCount - 1];
            final var strings = new String[constantPoolCount - 1];
            for (int i = 0; i < constantPoolCount - 1; i++) {
                int t = is.read();
                switch (t) {
                    case 1://utf-8
                        strings[i] = is.readUTF();
                        break;
                    case 3://Integer
                        is.readInt();
                        break;
                    case 4: // float
                        is.readFloat();
                        break;
                    case 5: // Long
                        is.readLong();
                        i++;
                        break;
                    case 6: // Double
                        is.readDouble();
                        i++;
                        break;
                    case 7: // Class index
                        classes[i] = is.readUnsignedShort();
                        break;
                    case 8: // string index
                        is.readShort();
                        break;
                    case 9: // field ref
                    case 10: // method ref
                    case 11: // interface method ref
                    case 12: // name and type
                    case 18: // invoke dynamic
                        is.readUnsignedShort();
                        is.readUnsignedShort();
                        break;
                    case 15: // method handle
                        is.read();
                        is.readUnsignedShort();
                        break;
                    case 16: // method type
                        is.readUnsignedShort();
                        break;
                    default:
                        throw new RuntimeException(format("Invalid constant pool tag %d at position %d", t,i));
                }
            }
            is.readShort(); // skip access flags
            final var classNameIndex = is.readShort();
            return strings[classes[(classNameIndex & 0xffff) - 1] - 1].replace('/', '.');
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
Peter Verhas
  • 268
  • 1
  • 6