invokevirtual on package private override in different classloader

David Holmes - Sun Microsystems David.Holmes at Sun.COM
Thu Apr 23 15:55:31 PDT 2009


Hi Karen,

I was okay with the explanation until this point:

 > 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.

I don't see the relevance of the quoted rule in this case, because this 
is not about the target of protected field/method access.

As far as I can see the reason there is no IllegalAccessError is because 
there is no illegal access:
- statically Printer's call to Square.getColor is fine as they are in 
the same compile-time package
- at run-time because CustomSquare.getColor does not override 
Square.getColor (because they are in different run-time packages), 
CustomSquare.getColor is not selected, but rather Square.getColor is
- At runtime Printer and Square are in the same runtime package, so 
Printer's invocation of Square.getColor is legal - even on a 
CustomSquare instance

In short there is no IllegalAccessError because CustomSquare.getColor is 
not selected to be called.

Cheers,
David Holmes

Karen Kinnear said the following on 04/24/09 07:35:
> 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