Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Class.getPackage return the same Package for classes from different packages?

I make a new ClassLoader and make it define a new Class, which means that new class should be in a new namespace, which it is, AFAIK. The strange thing is, when I call Class.getPackage on the new class, it returns the exact same object as returned by calling getPackage on any other class in my main namespace.

According to the JVM spec:

The runtime package of a class or interface is determined by the package name and defining class loader of the class or interface.

So in other words, if you have two classes in the same package, but are loaded by different classloaders, they are considered to be in different packages. (This can also be "confirmed" via reflection in my test case below.)

So howcome when I do this I get the same result from getPackage on both classes?

Here is my test:

package pkg;
import java.io.*;

// Yes, you can try commenting this class, you'll get the same result.
class LoadedClass {
    LoadedClass() {
        System.out.println("LoadedClass init");
    }
}

class MyClassLoader extends ClassLoader {
    Class<?> defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }
}

class Main {
    public static void main(String[] args) throws Exception {
        MyClassLoader mcl = new MyClassLoader();

        // load compiled class from file
        FileInputStream fileinputstream = new FileInputStream(
            "/home/me/test/pkg/LoadedClass.class" /* <- point to whever these classes
                                                   *    are being compiled to. */
        );
        int numberBytes = fileinputstream.available();
        byte classBytes[] = new byte[numberBytes];
        fileinputstream.read(classBytes);
        fileinputstream.close();

        Class<?> lc = mcl.defineClass("pkg.LoadedClass", classBytes);
        Package myPackage = Main.class.getPackage();
        Package lcPackage = lc.getPackage();
        System.out.println("lc package: " + lcPackage);
        System.out.println("my package: " + myPackage);
        System.out.println("lc ClassLoader: " + lc.getClassLoader());
        System.out.println("lc ClassLoader parent: " +
                           lc.getClassLoader().getParent());
        System.out.println("my ClassLoader: " + Main.class.getClassLoader());
        System.out.println("are they equal? " + (lcPackage == myPackage));
        if (lcPackage == myPackage) {
            System.out.println("okay... we should be able to instantiate " +
                               "the package if that's true, lets try");
            lc.newInstance(); // boom as expected
        }
    }
}

It outputs:

lc package: package pkg
my package: package pkg
lc ClassLoader: pkg.MyClassLoader@7987aeca
lc ClassLoader parent: sun.misc.Launcher$AppClassLoader@1f7182c1
my ClassLoader: sun.misc.Launcher$AppClassLoader@1f7182c1
are they equal? true
okay... we should be able to instantiate the package if that's true, lets try
Exception in thread "main" java.lang.IllegalAccessException: Class pkg.Main can not access a member of class pkg.LoadedClass with modifiers ""
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
    at java.lang.Class.newInstance0(Class.java:349)
    at java.lang.Class.newInstance(Class.java:308)
    at pkg.Main.main(Main.java:42)

As expected, you can't normally instantiate this loaded class via reflection, because package-private and it's in a different package (same name, different namespace), which is correct AFAIK, because it's enforcing type safety.

Just wondering because I've been studying the JVM and security architecture the last few days and keep finding little subtleties like this so it's hard to reason about.


Video Answer


1 Answers

The getPackage method is underspecified. Here's what bug 4256589 says about it:

ClassLoader.getPackage("foo") returns the package object defined for package foo in this particular class loader, or if this class loader didn't define package foo, the method returns what the parent class loader has defined for foo, if any.

To me, this says that the Package object returned by getPackage depends on whether the classloader "defined" a classes package itself, or if it found that package in its parent classloader. And the behavior are seeing seems to be consistent with this.

It is rather inconsistent. But does it really make any difference whether there is one package object or multiple package objects? Certainly, it shouldn't make any difference to type safety or to security ... unless you implemented some special package-based security scheme in a custom classloader or security manager.

like image 80
Stephen C Avatar answered Sep 28 '22 01:09

Stephen C