invokevirtual on package private override in different classloader

Karen Kinnear Karen.Kinnear at Sun.COM
Thu Apr 23 14:35:35 PDT 2009


Jeff,

Thank you so much for making this easy for me to reproduce.

I think you have two questions here:
1) why do you get "red" when you use a different class loader
for CustomSquare and
2) why do you not get an IllegalAccessError when calling
package private CustomSquare.getColour() from a
different runtime package.

These are very important questions and use cases related to
the first caused significant confusion in the past which
actually led to a specification modification.

Let me see if I can explain, first why "red", not "blue",
and second why you don't get an IllegalAccessError.

First, I too found that Printer and Square are loaded by the
same class loader, and CustomSquare is loaded by a different class
loader. I ran a fastdebug vm with the flag: 
-XX:+PrintSystemDictionaryAtExit. This will print out on exit, the
internal vm cache of loaded classes including their class loaders.

To summarize/abbreviate the scenario:

class Main: // class loader 1
    Printer.print((Square)CustomSquare instance);
package 1, Printer.print // class loader 1
     System.out.println(square.getColour())
package 1, class Square // class loader 1
     public getColour() returns "blue"
package 1, class customSquare // class loader 2
     package private getColour() returns "red"


The first question is why does the call in Printer to Square.getColour()
print "red" when the class customSquare is loaded by
a different class loader, but print "blue" when loaded by the
same class loader.

Let's start with the call in Printer. This translates to
invokevirtual(...Square.getColour())

The Clarifications and Amendments to Java Virtual Machine Specification, 
2nd edition definition of invokevirtual says:
http://java.sun.com/docs/books/jvms/second_edition/jvms-clarify.html

==
The lookup algorithm used by the invokevirtual  instruction (pages 
291-292 of the JVMS, 2nd edition) should be revised as follows:

     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 M overrides the resolved 
method , then M is the method to be invoked, and the lookup procedure 
terminates.

     Otherwise, if C has a superclass, this same lookup procedure is 
performed recursively using the direct superclass of C ; the method to 
be invoked is the result of the recursive invocation of this lookup 
procedure.

     Otherwise, an AbstractMethodError is raised.

The text highlighted in red is the only change. It replaces the text

     and the resolved method is accessible from C

in the current specification. See the revision of the definition of 
method override in the JLS 3rd Edition page for more details.
==


// First we do the static compile-time resolution using the constant 
pool, which in this case refers to Square.getColour()
The named method is resolved (§5.4.3.3).

// Then we do the dynamic run-time resolution using CustomSquare as the 
// target of the method invocation

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

// The key question is: Does CustomSquare.getColour() override
// Square.getColour()?
// I'll get to the definitions of inherit and override in a second.
// Result is:
// If they use the same class loader: yes CustomSqure.getColour() 
overrides Square.getColour()
// If they use different class loaders: no CustomSquare.getColor() does 
NOT override Square.getColour()

     * Otherwise, if C has a superclass, this same lookup procedure is 
performed recursively using the direct superclass of C ; the method to 
be invoked is the result of the recursive invocation of this lookup 
procedure.

// If CustomSquare.getColour() does NOT override Square.getColour(),
// then we use the method Square.getColour(), since Square is the
// direct superclass of CustomSquare.

Definitions of Inherit and Override:

===

Inheritance rules: JLS 3rd edition, 8.4.8 Inheritance, Overriding, and 
Hiding

   A class C inherits from its direct superclass and direct 
superinterfaces all non-private methods (whether abstract or not) of the 
superclass and superinterfaces that are public, protected or declared 
with default access in the same package as C and are neither overridden 
('8.4.8.1) nor hidden ('8.4.8.2) by a declaration in the class.

JLS 3rd edition 8.4.8.1 Overriding (by Instance Methods)

An instance method m1 declared in a class C overrides another instance 
method, m2, declared in class A iff all of the following are true:
       1. C is a subclass of A.
       2. The signature of m1 is a subsignature (§8.4.2) of the 
signature of m2.
       3. Either
          o m2 is public, protected or declared with default access in 
the same package as C, or
          o m1 overrides a method m3, m3 distinct from m1, m3 distinct 
from m2, such that m3 overrides m2.
Moreover, if m1 is not abstract, then m1 is said to implement any and 
all declarations of abstract methods that it overrides.
Note that based on the JVMS 3rd edition transitive overriding rules, we 
need to do an override check first for the direct superclass and if the 
current class does not override the direct superclass, recursively for 
the superclass' superclass.

===
So CustomSquare.getColour() does NOT override Square.getColour() because
it is NOT in the same runtime package as C.


===========
===========
Second question:
Why do you not get an IllegalAccessException when invoking 
package-private CustomSquare.getColour() from Printer.print() which
is in a different package?

We had the same question ourselves a couple of years ago and
researched this.

If you look closely at JVMS 2nd edition 5.4.4 Access Controls,
which goes into the details of accessibility checks for methods,
read the final sentences which say:
==

This discussion of access control omits a related restriction on the 
target of a protected field access or method invocation (the target must 
be of class D or a subtype of D). That requirement is checked as part of 
the verification process (§5.4.1); it is not part of link-time access 
control.
==

The point here is that the virtual machine in the case of invokevirtual,
does an accessibility check for the compile-time resolved method,
Square.getColour() in your example, but does NOT perform an
accessibility check on the link-time resolved method, or 
CustomSquare.getColour() in your example.

We proposed changing this behavior, and given that it has always been
this way, changing the specification and the multiple virtual
machines in the field would be highly likely to break multiple
shipping applications. So we decided it was not a good change
to make.

I hope this helps explain the current behavior, do let me
know if any of this needs further clarification or if I
misunderstood your original questions.

thanks,
Karen

p.s. you can get even more explanation of the inheritance, overriding, 
invokevirtual, invokespecial, etc. behavior, etc. in
the evaluation of the following bug:
http://monaco.sfbay.sun.com/detail.jsf?cr=4766230
p.p.s. The package private discussion is under
http://monaco.sfbay.sun.com/detail.jsf?cr=6810795


Jeffrey Sinclair wrote:
> Karen,
> 
> Just to say that there is one slight bug in my reproduction (although
> the result of 'Red' stays the same).
> 
> My debug statements for which class loader the class was loaded from is
> incorrect, they are done in the constructor when they should have been
> done in a static block in the class.
> 
> i.e. 
> 
> public class Square {
>   static {
>     System.out.println("Square loaded by classloader: " 
>         + Square.class.getClassLoader());
>   }
> 
> This will give the output:
> 
> Printer loaded by classloader: sun.misc.Launcher$AppClassLoader at 553f5d07
> Square loaded by classloader: sun.misc.Launcher$AppClassLoader at 553f5d07
> CustomSquare loaded by classloader: java.net.URLClassLoader at 3ae48e1b
> Red
> 
> Which is still unexpected since I'm getting 'Red'.
> 
> Regards,
> 
> Jeff
> 
> On Sat, 2009-04-18 at 17:32 +0100, Jeffrey Sinclair wrote:
> 
>>Karen,
>>
>>Please find attached a zip file which packages the example up with a
>>build script that uses javac and a plain old run script.
>>
>>Simply run build.sh followed by run.sh (assumes that javac and java are
>>on the PATH).
>>
>>Regards,
>>
>>Jeff
>>
>>On Thu, 2009-04-16 at 17:42 -0400, Karen Kinnear wrote:
>>
>>>Jeff,
>>>
>>>Thank you for sending me this information. I have a theory, but I
>>>would be much more comfortable running the test first. I have  
>>>experimented
>>>with this, but I'd feel much more comfortable duplicating your results.
>>>
>>>I do appreciate you offering to package this up using plain old javac/  
>>>and
>>>a run script - I don't have Eclipse installed and much as I've heard it
>>>is a great product, I don't have the cycles to do that in the near  
>>>future.
>>>
>>>thanks so much,
>>>Karen
>>>
>>>On Apr 14, 2009, at 5:33 PM, Jeffrey Sinclair wrote:
>>>
>>>
>>>>Karen,
>>>>
>>>>Thanks for getting back to me.
>>>>
>>>>I was using 1.6.0_10 and have now tried 1.6.0_13 and get the same
>>>>result. Specifically I've tried the following versions:
>>>>
>>>>java version "1.6.0_10"
>>>>Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
>>>>Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)
>>>>
>>>>java version "1.6.0_13"
>>>>Java(TM) SE Runtime Environment (build 1.6.0_13-b03)
>>>>Java HotSpot(TM) 64-Bit Server VM (build 11.3-b02, mixed mode)
>>>>
>>>>I've attached my source as Eclipse projects. The URL in the main  
>>>>method
>>>>points to the class files generated by the project with the
>>>>CustomSquare. I've also added some debug info relating to which class
>>>>loader is being used to load the Square, Printer and Custom Square
>>>>respectively:
>>>>
>>>>Printer loaded by classloader: sun.misc.Launcher 
>>>>$AppClassLoader at 553f5d07
>>>>Square loaded by classloader: java.net.URLClassLoader at 3ae48e1b
>>>>CustomSquare loaded by classloader: java.net.URLClassLoader at 3ae48e1b
>>>>Red
>>>>
>>>>I still get Red. The key point is that the CustomSquare is not visible
>>>>at all to the Printer code. If I stick the CustomSquare in the same
>>>>project as Printer it will then be loaded by the AppClassLoader and I
>>>>get blue. However when it is in a completely separate project and  
>>>>loaded
>>>>via the URLClassLoader (and Printer loaded via the AppClassLoader) I
>>>>always get Red. If I then change getColour() to be public in both  
>>>>Square
>>>>and CustomSquare I get blue.
>>>>
>>>>This is completely baffling me. I may be doing something really silly
>>>>but I can't see it :)
>>>>
>>>>Let me know if you can't replicate the issue with the attached Eclipse
>>>>project and I'll try to package it up using plain old javac and a run
>>>>script.
>>>>
>>>>Even if you do get blue with the different ClassLoaders for Printer  
>>>>and
>>>>CustomSquare, I would probably argue that this is incorrect but I have
>>>>to admit that this argument could be because I've misunderstood the
>>>>meaning of 'runtime package' in terms of package private accessibility
>>>>in the JVM spec.
>>>>
>>>>Jeff
>>>>
>>>>On Tue, 2009-04-14 at 15:30 -0400, Karen Kinnear wrote:
>>>>
>>>>>Jeff,
>>>>>
>>>>>Perhaps you can help me duplicate the problem.
>>>>>
>>>>>First - what does java -version say?
>>>>>
>>>>>I took the source code appended, and modified the URL setting,
>>>>>which I believe should just give me your second example, i.e.
>>>>>CustomSquare instantiated in a different ClassLoader than Printer.
>>>>>
>>>>>When I run this with Sun's 1.6 or recent 1.7 I get "blue".
>>>>>
>>>>>What did we do differently?
>>>>>
>>>>>thanks,
>>>>>Karen
>>>>>
>>>>>Jeffrey Sinclair wrote:
>>>>>
>>>>>>hotspot-dev,
>>>>>>
>>>>>>I've been struggling to understand why HotSpot does not throw an
>>>>>>IllegalAccessError when a call is made to an override of a package
>>>>>>private method on an instance loaded by a different class loader.  
>>>>>>I was
>>>>>>hoping someone could explain to me in technical detail where I'm  
>>>>>>going
>>>>>>wrong because it appears to be a bug.
>>>>>>
>>>>>>(all source code is pasted at the end of the mail)
>>>>>>
>>>>>>I have a class called Square with a package private method named
>>>>>>getColour() which returns 'red'. I have a subclass of Square called
>>>>>>CustomSquare which overrides getColour() to return 'blue'. I have
>>>>>>another class called Printer which simply prints out getColour()  
>>>>>>for the
>>>>>>Square passed to it. I then have two test cases:
>>>>>>
>>>>>>  * Printer.print(Square) is called with a CustomSquare  
>>>>>>instantiated in
>>>>>>the same ClassLoader as Printer.
>>>>>>  * Printer.print(Square) is called with a CustomSquare  
>>>>>>instantiated in
>>>>>>a different ClassLoader as Printer.
>>>>>>
>>>>>>What I find is that I get 'blue' in the first test (as expected) and
>>>>>>'red' in the second test (not expected).
>>>>>>
>>>>>>
>>>>>>>From the Access Control constraints in the Linking section of the  
>>>>>>>JVM
>>>>>>
>>>>>>specification (5.4.4), I as expecting an IllegalAccessError to be  
>>>>>>thrown
>>>>>>because it states that a package private method is accessible to a  
>>>>>>class
>>>>>>if it is declared by a class in the same runtime package.
>>>>>>
>>>>>>My understanding is that Printer is not in the same runtime  
>>>>>>package as
>>>>>>CustomSquare which explains why my override does not kick in, but it
>>>>>>does not explain why an IllegalAccessError is not thrown.
>>>>>>
>>>>>>I was wondering if someone could explain the behaviour to me.
>>>>>>
>>>>>>Regards,
>>>>>>
>>>>>>Jeff
>>>>>>
>>>>>>The source code:
>>>>>>
>>>>>>public class Main {
>>>>>> public static void main(String[] args) throws Exception {
>>>>>>   URL[] urls = new URL[]{new URL("path/to/CustomSquare")};
>>>>>>   URLClassLoader loader = new URLClassLoader(urls);	
>>>>>>   Class clazz =
>>>>>>loader.loadClass("uk.co.cooljeff.visibility.CustomSquare");
>>>>>>   Printer printer = new Printer();
>>>>>>   printer.print((Square)clazz.newInstance()); // 'red' gets printed
>>>>>> }	
>>>>>>}
>>>>>>
>>>>>>package uk.co.cooljeff.visibility;
>>>>>>
>>>>>>public class Printer {
>>>>>> public void print(Square square) {
>>>>>>   System.out.println(square.getColour());
>>>>>> }
>>>>>>}
>>>>>>
>>>>>>package uk.co.cooljeff.visibility;
>>>>>>
>>>>>>public class CustomSquare extends Square {
>>>>>> public CustomSquare() {
>>>>>>   super(5);
>>>>>> }
>>>>>>
>>>>>> @Override
>>>>>> public String getColour() {
>>>>>>   return "blue";
>>>>>> }
>>>>>>}
>>>>>>
>>>>>>package uk.co.cooljeff.visibility;
>>>>>>
>>>>>>public class Square {
>>>>>> private float length;
>>>>>>
>>>>>> public Square(float length) {
>>>>>>   this.length = length;
>>>>>> }
>>>>>>
>>>>>> public float calculateArea() {
>>>>>>   return length * length;
>>>>>> }
>>>>>>
>>>>>> String getColour() {
>>>>>>   return "red";
>>>>>> }
>>>>>>}
>>>>>>
>>>>>
>>>><DefaultVisibilityOverride.zip>
>>>
> 




More information about the hotspot-dev mailing list