Search code examples
javajvminvokedynamic

Retrieve Array Clone Method Handle Through a Public Lookup


Recently, I attempted to access the clone method of an array type through the java.lang.invoke library. This proved unsuccessful. The clone method in sample is one such as this:

int[] a = new int[]{1, 2, 3, 4};
int[] b = a.clone();

I wish to create a MethodHandle for the a.clone() call. That way the resulting code is similar to:

int[] a = new int[]{1, 2, 3, 4};
int[] b = findCloneMethod().invoke(a);

I have this system set up for every other method invocation. However, the system fails with only this method.

This question pertains to the java.lang.invoke library, not the java.lang.reflect library.

Problem Description

The following sample code was introduced to show this behavior.

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class Main {

    void test() {
        final MethodHandles.Lookup caller = MethodHandles.lookup();
        final Class<?> refc = int[].class;
        final String name = "clone";

        final MethodType type = MethodType.methodType(Object.class);

        final MethodHandle mh = findVirtual(caller, refc, name, type);

        System.out.println("Lookup: " + caller);
        System.out.println("mh: " + mh);
    }

    public static void main(String[] args) {
        new Main().test();
    }

    private MethodHandle findVirtual(final MethodHandles.Lookup caller, final Class<?> refc, final String name, final MethodType type) {
        try {
            return caller.findVirtual(refc, name, type);
        } catch (final Throwable t) {
            t.printStackTrace();
            return null;
        }
    }
}

Sample output:

Lookup: Main

mh: MethodHandle(Main)Object


The output makes sense. The caller, which is from the scope of Main only sees the clone method in Main's superclass: Object. Even though int[] is used as the reference class, this method is not visible. This becomes an issue, since if this callsite is used, the JVM attempts to cast int[] to Main.

By introducing the reference type as int[], one would expect that the clone method in type int[] is publicly accessible. Therefore, when the documentation suggests:

The type of the method handle will be that of the method, with the receiver type (usually refc) prepended. 1

That the type of mh would be (int[])Object.


Attempted solution:

Since the Lookup caller is interfering by shadowing the proper clone method, an attempt was made by using the public lookup:

final MethodHandles.Lookup caller = MethodHandles.publicLookup();

However, this fails. Simple inspection of the Object#clone() signature shows why:

protected Object clone() throws CloneNotSupportedException { ...

The method is protected. This is not true for array types, as the implementation makes this method publicly available.

An example of this would be trying to access the Object#equals method. This is shown with three separate reference classes:

final MethodHandles.Lookup caller = MethodHandles.publicLookup();
final Class<?> refc = Object.class;
final String name = "equals";

final MethodType type = MethodType.methodType(boolean.class, Object.class);

Lookup: java.lang.Object/public

mh: MethodHandle(Object,Object)boolean

And then using Main.class as the reference class:

final Class<?> refc = Main.class;

Lookup: java.lang.Object/public

mh: MethodHandle(Main,Object)boolean

And then using int[].class as the reference class:

final Class<?> refc = int[].class

Lookup: Main

mh: MethodHandle(int[],Object)boolean

This is the intended behavior, obviously works fluently. In the first case, the reference class was Object and it returned the Object#equals method handle. In the second, the reference class was Main and it returned the Main#equals method handle. And finally, the reference class was int[].class and it returned the int[]#equals method handle.

This parity is not maintained with the int[]#clone method though. Since the clone method is protected, but public for arrays, it can't be found. To me this seems like a bug. The array clone method should be made publicly available through the publicLookup. The returned method handle type could be: MethodHandle(Object)Object.

Once retrieved, the MethodHandle#asType can then be used to correct the type. In this case, it would be casted to MethodHandle(int[])Object.

Without the Object#clone method being publicly available, at least for array types, this seems like its not possible.

Furthermore, this seems like the only method where this can possibly happen, as the array types only extend/implement 3 classes: Object, Cloneable and Serializable. The only other method would be Object#finalize which is also protected; however, array types do not make this method publicly available. Therefore clone is the only method that this fails for.


tl;dr:

The array clone method, such as Object[]#clone(), is not publicly accessible through the public lookup. This is because Object#clone is protected; however, array types make this method publicly available. Trying to access the array clone through the reference class fails due to this visibility issue. As a result, a MethodHandle cannot be generated for this method. However, trying the same techniques on public methods such as Object#equals works fine for array types.


I would prefer to avoid reflection access, as the methods are there to retrieve it by simply changing the trust level of a lookup.

Is there any way to generate a MethodHandle for the array clone method?


Note: I do understand the proper usage of java.lang.invoke and I do not intend to use this as a replacement for java.lang.reflect.


Solution

  • There was a bug JDK-8001105 in early implementations of JSR 292 where findVirtual did not work for array's clone method. However, this problem has been fixed a long ago.

    So, in recent JDK 8 findVirtual works fine for clone if called on publicLookup().
    You may find a special case for this in JDK source code:

            if (Modifier.isProtected(mods) &&
                    refKind == REF_invokeVirtual &&
                    m.getDeclaringClass() == Object.class &&
                    m.getName().equals("clone") &&
                    refc.isArray()) {
                // The JVM does this hack also.
                // (See ClassVerifier::verify_invoke_instructions
                // and LinkResolver::check_method_accessability.)
                // Because the JVM does not allow separate methods on array types,
                // there is no separate method for int[].clone.
                // All arrays simply inherit Object.clone.
                // But for access checking logic, we make Object.clone
                // (normally protected) appear to be public.
                // Later on, when the DirectMethodHandle is created,
                // its leading argument will be restricted to the
                // requested array type.
                // N.B. The return type is not adjusted, because
                // that is *not* the bytecode behavior.
                mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
            }