【问题标题】:Retrieve Array Clone Method Handle Through a Public Lookup通过公共查找检索数组克隆方法句柄
【发布时间】:2018-05-09 19:36:42
【问题描述】:

最近尝试通过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 库有关,不是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;
        }
    }
}

样本输出:

查找:主要

mh: MethodHandle(Main)Object


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

通过将引用类型引入int[],可以预期int[] 类型中的clone 方法可以公开访问。因此,当文档建议时:

方法句柄的类型将是方法的类型,接收者类型(通常是 refc)在前面。 1

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


尝试的解决方案:

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

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

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

protected Object clone() throws CloneNotSupportedException { ...

方法是protected。这不适用于数组类型,因为实现使该方法公开可用。

这方面的一个例子是尝试访问Object#equals 方法。这显示为三个独立的参考类:

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

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

查找:java.lang.Object/public

mh: MethodHandle(Object,Object)boolean

然后使用Main.class作为引用类:

final Class<?> refc = Main.class;

查找:java.lang.Object/public

mh: MethodHandle(Main,Object)boolean

然后使用int[].class作为引用类:

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

查找:主要

mh: MethodHandle(int[],Object)boolean

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

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

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

如果没有公开可用的Object#clone 方法,至少对于数组类型来说,这似乎是不可能的。

此外,这似乎是唯一可能发生这种情况的方法,因为数组类型仅扩展/实现 3 个类:ObjectCloneableSerializable。唯一的其他方法是Object#finalize,它也受到保护;但是,数组类型不会公开此方法。因此clone 是唯一失败的方法。


tl;博士:

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


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

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


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

【问题讨论】:

  • 你用的是什么版本的java?我在使用lookup() 时遇到了与(Main)Object 相同的问题,但它使用publicLookup() 找到了正确的方法。实际上,这似乎也适用于 1.8:ideone.com/vMmWMr
  • @JornVernee 我更新了我的 Java 版本,这似乎工作正常。我最初运行的是 1.8(忘记检查次要版本)。此外,指定的引用类型是错误的Object.class。第二个问题产生了一个不同的错误,所以我猜这是两者的结合。我会将问题留待任何答案,如果没有人这样做,我将在稍后总结社区答案。谢谢!

标签: java jvm invokedynamic


【解决方案1】:

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

【讨论】:

  • 太棒了!长话短说:更新您的 Java 版本
  • 好的.....但是为什么它不适用于其他查找对象,即不是publicLookup()?与 OP 不同,我不认为“输出有意义”。事实上,我认为将数组类型替换为其他查找对象的完全不相关的调用者类并没有任何意义。
猜你喜欢
  • 2023-01-11
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-18
  • 1970-01-01
  • 2014-11-01
  • 1970-01-01
相关资源
最近更新 更多