When protected isn’t protected
Written by Jevgeni Kabanov on November 3, 2008 – 1:41 pmFor all those spec believers out there: what would you think if I told you that all of the JVM implementations consistently violate the spec for at least one particular instance?
Our story begins on a cold and lonely evening when I discover a bug in JavaRebel in conjunction with Xerces XMLEntityManager. The problem was with the following method:
However when I looked at the class bytecode I saw a different picture:
For those of you who do not spend hours staring at the Java bytecode, it does the following:
- Retrieve the
RPROPERTIESstatic field value (which is of typeString[]) and put it on the top of the stack. - Call
Object.clone()using the top of the stack as target object (in our case the array). The target is popped off the stack and the result is placed on top of the stack instead of it. - Cast the top of the stack to a
String[] - Return the top of the stack as method result.
Whooph. Now that is all fine except for one tiny thing. This is not legal. Object.clone() is a protected method and the JVM spec (see 2.7.4, INVOKEVIRTUAL) allows calls to protected method only if the following is true:
- The accessing class is a subclass of the target class or is the same class (check,
XMLEntityManageris a subclass ofObject, so’s everything else). - The class of the target object (the actual runtime class of the object, not the target class we use in the signature) is either a subclass of the accessing class or is the same class.
The second check may sound crazy, but it restricts the calls to protected methods only to the classes inheriting from the target class, as you would expect. It can also only be enforced during runtime and is the cause of the IllegalAccessError you may see once in a while.
And obviously in our case String[] that is the actual type of the target object is in no way a subclass of the XMLEntityManager. This call should have cause an error in the JVM, but for some inexplicable reason it doesn’t. Further tests show that this is only limited (AFAIK) to the Object.clone() call, which is public as far as JVM cares.
A little googling turned up two JVM bugs that were discussing the same issue (bug 1, bug 2). Turns out the Sun javac compiler has been emitting wrong code at least until 1.4.2 and the JVMs have accommodated accordingly right until today.
Does this have any practical importance? Not for most of you, but if you handle Java bytecode in any way be prepared to handle this exception as well. To me it proves that there is more to Java than just the spec.
Tags: bytecode, java
Posted in Featured, creative | 8 Comments »
8 Comments to “When protected isn’t protected”
Leave a Comment
Additional comments powered by BackType
November 3rd, 2008 at 7:13 pm
I rarely have to delve into byte code, so I’m just commenting based on about 10 minutes of spec reading: :)
Invokevirtual does dispatching based on the runtime type of the target object. So where the byte code nominally shows INVOKEVIRTUAL Object.clone() : Object[], could it be that it’s actually dispatching to Array[].clone()?
If that is true, then according to the JLS 6.4.5, the clone() method on all array types is actually public.
That might be one way to explain it. :)
November 3rd, 2008 at 8:52 pm
Sometimes, when an implementation bug like this occurs, a certain mass of code appears that depends upon it.
This causes big problems when it comes time to fix it since it’ll break existing code bases. I say, it’s better to just fix things. Sometimes, we should just let go of backward compatibility.
November 4th, 2008 at 7:19 am
The superclass of all array types is Object…
November 4th, 2008 at 2:42 pm
@Jing, @Tim
You are missing the point. If you write this code in Java it won’t even compile:
public String[] getRecognizedProperties() { return (String[])(((Object) RPROPERTIES).clone()); }November 4th, 2008 at 2:45 pm
@Paul
Actually most often that’s a terrible idea. Backwards-compatibility is very important, unless you want to get up at 3 AM because the production server doesn’t work properly after the latest Java update.
My point is more that people who think that Java spec is all there is to Java are greatly oversimplifying things.
November 4th, 2008 at 3:25 pm
Sneaky bug.
The JVM allows access to all members (incl. private and protected) through reflection – but at least that’s done explicitly.
November 4th, 2008 at 6:56 pm
@Jevgeni,
Of course that’s not going to compile, but that’s governed by the Java _Language_ Spec, not the _JVM_ Spec, isn’t it?
So if we stick to the JVM and the byte code, it does seem to me that INVOKEVIRTUAL is doing what it’s supposed to do: dispatching to the right version at runtime, instead of going with whatever is nominally passed to it.
November 4th, 2008 at 7:08 pm
@Jing
Yes, I just brought it as an easier to understand example. JVM must also enforce the Java access rules, one of them is that “protected” members can only be accessed by either members of the same package or subclasses. Verifier cannot check the subclass restriction fully, therefore INVOKEVIRTUAL does an extra check (quoted):
String[]is obviously *not* a subclass ofXMLEntityManagertherefore this call should not be allowed. If you check the bug reports I’m referring, it is clear that the JVM engineers are aware that this is a bug.