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