通过 Public 查找检索数组克隆方法句柄

Retrieve Array Clone Method Handle Through a Public Lookup

最近,我试图通过java.lang.invoke库访问数组类型的clone方法。这被证明是不成功的。示例中的克隆方法是这样的:

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

我想为 a.clone() 调用创建一个 MethodHandle。这样生成的代码类似于:

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

我为每个其他方法调用设置了这个系统。但是,系统仅使用此方法失败。

此问题与 java.lang.invoke library, not the java.lang.reflect 图书馆有关。

问题描述

引入了以下示例代码来展示此行为。

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

示例输出:

Lookup: Main

mh: MethodHandle(Main)Object


输出有意义。来自 Main 范围的调用者只能在 Main 的 superclass: Object 中看到 clone 方法。即使使用 int[] 作为参考 class,此方法也不可见。这成为一个问题,因为如果使用此调用站点,JVM 会尝试将 int[] 转换为 Main

通过将引用类型引入 int[],人们会期望类型 int[] 中的 clone 方法可以 public 访问。因此,当文档提示:

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

mh 的类型将是 (int[])Object


尝试的解决方案:

由于查找 caller 通过隐藏正确的 clone 方法进行干扰,因此尝试使用 public 查找:

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

然而,这失败了。 Object#clone() 签名的简单检查说明了原因:

protected Object clone() throws CloneNotSupportedException { ...

方法是protected。这对于数组类型而言并非如此,因为实现使此方法 public 可用。

这方面的一个例子是尝试访问 Object#equals 方法。这显示了三个单独的参考 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

然后用Main.class作为参考 class:

final Class<?> refc = Main.class;

Lookup: java.lang.Object/public

mh: MethodHandle(Main,Object)boolean

然后用int[].class作为参考 class:

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

Lookup: Main

mh: MethodHandle(int[],Object)boolean

这是预期的行为,显然可以流畅地运行。在第一种情况下,引用 class 是 Object 并且它返回 Object#equals 方法句柄。在第二个中,引用 class 是 Main 并且它返回 Main#equals 方法句柄。最后,引用 class 是 int[].class 并且它返回了 int[]#equals 方法句柄。

这个奇偶校验 而不是 int[]#clone 方法维护。由于clone方法是受保护的,但是public对于数组,是找不到的。对我来说,这似乎是一个错误。数组 clone 方法应该 public 通过 publicLookup 可用。返回的方法句柄类型可以是:MethodHandle(Object)Object.

检索后,MethodHandle#asType 可用于更正类型。在这种情况下,它将被转换为 MethodHandle(int[])Object

如果 Object#clone 方法 public 不可用,至少对于数组类型,这似乎是不可能的。

此外,这似乎是唯一可能发生这种情况的方法,因为数组类型只有 extend/implement 3 classes:ObjectCloneableSerializable。唯一的其他方法是 Object#finalize ,它也受到保护;但是,数组类型并不使此方法 public 可用。因此 clone 是唯一失败的方法。


tl;博士:

数组 clone 方法,例如 Object[]#clone(),public 无法通过 public 查找访问。这是因为 Object#cloneprotected;但是,数组类型使此方法 public 可用。由于此可见性问题,尝试通过引用 class 访问数组 clone 失败。因此,无法为该方法生成 MethodHandle。但是,在 public 方法(例如 Object#equals 上尝试相同的技术对于数组类型来说效果很好。


我宁愿避免反射访问,因为那里的方法可以通过简单地更改查找的信任级别来检索它。

有没有办法为数组clone方法生成MethodHandle


注意:我确实了解 java.lang.invoke 的正确用法,我不打算用它来替代 java.lang.reflect

JSR 292 的早期实现中存在错误 JDK-8001105,其中 findVirtual 不适用于数组的 clone 方法。不过这个问题早就解决了

因此,最近 JDK 8 findVirtual 如果在 publicLookup().
上调用,则 clone 工作正常 您可以在 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;
        }