Archive

Archive for May, 2009

The subtleties of overriding package private methods

May 3rd, 2009

The Java Language comes with several modifiers for controlling access to methods: public, private, protected and package private.

In general we assume the following rules govern these access modifiers:

  • A public method can be accessed by any other method.
  • A private method can only be accessed by the class that it is declared in.
  • A protected method can be accessed by any class in the same package or any subclass.
  • A package private method can be accessed by any class in the same package.

The above would be accepted as suitable answers at most interviews, however there are some subtleties seen in the behaviour of package private that cannot be explained using this simple definition alone.

Take the following piece of code that represents a Square:

 
package uk.co.cooljeff.access;
public class Square {
  private float length;
  public Square(float length) {
    this.length = length;
  }
  public float calculateArea() {
    return length * length;
  }
  String getColour() {
    return "Red";
  }
}

Take the following piece of code that represents a CustomSquare:

 
package uk.co.cooljeff.access;
import uk.co.cooljeff.access.Square;
public class CustomSquare extends Square {
  public CustomSquare() {
    super(5);
  }
  @Override
  String getColour() {
    return "Blue";
  }
}

From the simple definition of package private above, we would expect the following piece of code to print out Blue as the colour of CustomSquare:

 
public class Printer {
  public void print(Square square) {
    System.out.println("Square of type " + square.getClass()
                                   + " has colour " + square.getColour());
  }
  public static void main(String[] args) {
    Printer printer = new Printer();
    printer.print(new CustomSquare());
  }
}

For the majority of people who run the above code, indeed Blue is printed. However for one of my colleagues at work who tried to do something similar, Red was always observed.

To try to explain why Red is sometimes seen, we need to understand what the JVM is trying to do. If you decompile the Printer class you will see that the invokevirutal instruction is used to invoke the Square.getColour() method:

 
public void print(uk.co.cooljeff.access.Square);
  Code:
   25:	invokevirtual	#59; //Method uk/co/cooljeff/access/Square.getColour:()Ljava/lang/String;
   37:	return

For virtual methods, there is a lookup algorithm which is used to locate the exact method to invoke. Specifically the following is given for the invokevirtual instruction (paraphrasing):

Let C be the class of the target of the method invocation. The actual method to be invoked is selected by the following lookup procedure:

  • If C contains a declaration for an instance method M with the same name and descriptor as the resolved method, and the resolved method is accessible from C, then M is the method to be invoked, and the lookup procedure terminates.

Hence using this definition, I would expect that CustomSquare.getColour() would be considered since it matches the same name and descriptor as Square.getColour(). The question then becomes is this method accessible from Printer. To answer this we need to look at the Access Control (5.4.4) definition in the JVM Specification which states (paraphrasing):

A field or method R is accessible to a class or interface D if and only if any of the following conditions are true:

  • R is package private and is declared by a class in the same runtime package as D.

There is a subtlety here in that the phrase runtime package is used. At runtime a class is uniquely defined by its fully qualified name and its ClassLoader. The runtime package takes into account not only the compile time package but also the ClassLoader that loaded the class.

Taking the fact that the ClassLoader could have something to do with why Red is sometimes observed, lets redefine our Printer to load the CustomSquare in a different ClassLoader:

 
package uk.co.cooljeff.access;
import java.net.URL;
import java.net.URLClassLoader;
public class Printer {
  public void print(Square square) {
    System.out.println("Square of type " + square.getClass() + " has colour " + square.getColour());
  }
  public static void main(String[] args) throws Exception {
    // Path to a jar or classes containing CustomSquare.
    String customSquareURL = System.getProperty("customsquare.classpath");
    URL[] urls = new URL[] { new URL( customSquareURL ) };
    URLClassLoader loader = new URLClassLoader(urls);
    Class clazz = loader.loadClass("uk.co.cooljeff.access.CustomSquare");
    Printer printer = new Printer();
    printer.print((Square) clazz.newInstance());
  }
}

When CustomSquare is loaded by a different ClassLoader, the output is Red, which is what my colleague observed.

At this stage, although the behaviour is reproducible, it cannot be explained using the 2nd Edition of the JVM Specification alone. The specification clearly states in the definition of method resolution, that an IllegalAccessError should be thrown if the method is not accessible and the definition of accessibility clearly states that CustomSquare is not accessible. So why do we not get an IllegalAccessError in either Sun’s HotSpot JVM or IBM’s JVM?

The behaviour made sense to me because it provides a way to prevent hacks such as the one my colleague was attempting to do, however I could not prove it using the JVM Specification. It was time to call on the experts so I started a mailthread titled invokevirtual on package private override in different classloader on the hotspot-dev mailing list.

As it turns out there was an amendment made to the 2nd Edition of the JVM Specification relating to this behaviour which revises the definition of invokevirtual. Specifically it replaces the accessibility constraint in favour for an override constraint. Hence to explain the behaviour we don’t look at whether CustomSquare.getColour() is accessible, we need to determine if it overrides Square.getColour().

The answer to the override question is no, which explains why the behaviour is observed. However as pointed out on the HotSpot mail thread, I was not happy that the amendment to the JVM specification refered to a language definition (namely override). At the moment in order to explain the behaviour we need to use the revised definition of override in the Java Language Specification. This goes against the fundamental principle that the Java Virtual Machine Specification is decoupled from the Java Language Specification. This was acknowledged on the mailthread: Alex Buckley replied informing me that the 3rd edition of the JVM specification is independent of the Language specification. Specifically the 3rd edition (not publicly available yet) drops the chapter on the Java Programming Language Concepts entirely and gains a JVM specific definition of overrides.

So I’m happy now, I can explain the behaviour :)

I’d like to thank the following people who helped me understand the behaviour discussed in this blog entry: Karen Kinnear (Sun Microsystems), David Holmes (Sun Microsystems) and Alex Buckley (Sun Microsystems).

Resources:

java