When protected isn’t protected

For 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:
[java]
public String[] getRecognizedProperties() {
return (String[])(RPROPERTIES.clone());
}
[/java]
However when I looked at the class bytecode I saw a different picture:
[java]
{
GETSTATIC XMLEntityManager.RPROPERTIES : String[]
INVOKEVIRTUAL Object.clone() : Object[]
CHECKCAST String[]
ARETURN
}
[/java]
For those of you who do not spend hours staring at the Java bytecode, it does the following:

  1. Retrieve the RPROPERTIES static field value (which is of type String[]) and put it on the top of the stack.
  2. 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.
  3. Cast the top of the stack to a String[]
  4. 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:

  1. The accessing class is a subclass of the target class or is the same class (check, XMLEntityManager is a subclass of Object, so’s everything else).
  2. 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: ,

  • http://www.digizenstudio.com/blog Jing Xue

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

  • http://willcode4beer.com Paul

    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.

  • Tim

    The superclass of all array types is Object…

  • Jevgeni Kabanov

    @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());
    }
    
  • Jevgeni Kabanov

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

  • http://orip.org orip

    Sneaky bug.
    The JVM allows access to all members (incl. private and protected) through reflection – but at least that’s done explicitly.

  • http://www.digizenstudio.com/blog Jing Xue

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

  • Jevgeni Kabanov

    @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):

    Finally, if the resolved method is protected (§4.6), and it is either a member of the current class or a member of a superclass of the current class, then the class of objectref must be either the current class or a subclass of the current class.

    String[] is obviously *not* a subclass of XMLEntityManager therefore 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.