Search code examples
javareflectionjava-platform-module-system

Java 14 Jigsaw reflection to a class in not a Exported package is not denying access


I am playing with Jigsaw. I have a simple reproducible code.

package university.harvard;

public class Pilot{
    public static void main(final String args[]){
        callInNotAExportedPackage();
    }
    private static void callInNotAExportedPackage(){
        try{
            final Class<?>classy = Class.forName("javax.swing.JButton");
            System.out.println(classy.newInstance());
        }catch(final Exception e){
            e.printStackTrace();
        }
    }           
}

I have module-info.java like this.

module John{
    exports university.harvard;
}

I can compile the module this this command.

C:\Ocp11>javac -d out --module-source-path src -m John

Note: src\John\Pilot.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

I'm getting messages about deprecation but the compilation is successful. At this point I say well the compiler doesn't know I will try to call a class in not a exported package.

And when I run the module

C:\Ocp11>java -p out -m John/university.harvard.Pilot

I can see that the instance is being retrieved by reflection with not a problem.

javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.
swing.plaf.BorderUIResource$CompoundBorderUIResource@3498ed,flags=296,maximumSiz
e=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,
margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14],paintB
order=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rollo
verSelectedIcon=,selectedIcon=,text=,defaultCapable=true]

But what is this? I thought that Jigsaw would block me

If I put the fully qualified class name in the code like this.

final Class<?>classy = Class.forName("javax.swing.JButton");
final javax.swing.JButton button = (javax.swing.JButton)classy.newInstance();
System.out.println(button);         

And this time Jigsaw reacts correctly.

C:\Ocp11>javac -d out --module-source-path src -m John
src\John\Pilot.java:10: error: package javax.swing is not visible
            final javax.swing.JButton button = (javax.swing.JButton)classy.newIn
stance();
                       ^
  (package javax.swing is declared in module java.desktop, but module John does
not read it)
src\John\Pilot.java:10: error: package javax.swing is not visible
            final javax.swing.JButton button = (javax.swing.JButton)classy.newIn
stance();
                                                     ^
  (package javax.swing is declared in module java.desktop, but module John does
not read it)
Note: src\John\Pilot.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
2 errors

But then I don't put the fully qualified class name I can bypass Jigsaw.

I know that there is not much I can do without a reference to it but I think is some strange that Jigsaw allow me to do so.

I'm using

C:\Ocp11>java --version
java 14 2020-03-17
Java(TM) SE Runtime Environment (build 14+36-1461)
Java HotSpot(TM) 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)

  

Solution

  • The question's title says

    Java 14 Jigsaw reflection to a class in not a Exported package is not denying access

    But actually, the class you're trying to access is in an exported package. The class javax.swing.JButton is in package javax.swing of module java.desktop and that package is exported:

    // [License & Javadoc]
    module java.desktop {
        // [other exports and requires]
        exports javax.swing;
        // [other exports, opens, uses and provides]
    }
    

    As the module is available at runtime, Class.forName("javax.swing.JButton") will not throw an exception (otherwise it would throw a java.lang.ClassNotFoundException). It probably is available, because it is a root module (see here and here).

    Even if the class would not be in an exported package this would work:

    Class<?> aClass = Class.forName("sun.java2d.marlin.Curve"); // will compile and run
    

    However, (javax.swing.JButton)classy.newInstance(); will not compile — not because the package is not exported, but because your module, John, does not read it. For this to work, your module would a requires java.desktop; like this:

    module John {
        requires java.desktop;
    
        exports university.harvard;
    }
    

    This way the module John would be able to read all of java.desktop's exported packages.

    Calling classy.newInstance(); (without the type cast) will compile because the compiler does not know the type of the new instance.

    Here are some examples what will work at runtime and compile time and why (not):

    // (1) will compile and run:
    Class<?> curveClass = Class.forName("sun.java2d.marlin.Curve");
    
    // (2) will compile but not run, even if the module `requires java.desktop;`:
    Object curveObject = curveClass.newInstance();
    
    // (3) will not compile and not run, even if the module has `requires java.desktop;`:
    sun.java2d.marlin.Curve curve = (sun.java2d.marlin.Curve) curveClass.newInstance();
    
    // (4) will compile and run:
    Class<?> jButtonClass = Class.forName("javax.swing.JButton");
    
    // (5) will compile and run, even if the module does not have `requires java.desktop;`:
    Object jButtonObject = jButtonClass.newInstance();
    
    // (6) will only compile if the module `requires java.desktop;`:
    javax.swing.JButton jButton = (javax.swing.JButton) jButtonClass.newInstance();
    
    1. Holds only information / characteristics of the class nothing more, so this kind of access is ok.
    2. Will compile because the compiler does not know that the new instance is of type sun.java2d.marlin.Curve. At runtime however, the access will be blocked.
    3. Will not even compile because the compiler knows the type and therefore knows that the access would be illegal.
    4. Same as (1)
    5. Will compile because of the same reason as with (2) and will run because the package is actually exported
    6. Will only compile if the module has requires java.desktop; because the compiler knows the type