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