2

我想使用 LambdaMetaFactory 来有效地访问私有方法。

public class Foo {
  private void bar() {  // here's what I want to invoke
    System.out.println("bar!");
  }
}

我知道这不是安全违规,因为以下代码有效:

Foo foo = new Foo();
Method m = Foo.class.getDeclaredMethod("bar");
m.setAccessible(true);
m.invoke(foo);  // output: bar!

但是,我尝试使用 LambdaMetaFactory 失败了:

MethodHandles.Lookup lookup = MethodHandles.lookup();
Method m = Foo.class.getDeclaredMethod("bar");
m.setAccessible(true);

CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
        MethodType.methodType(Consumer.class),
        MethodType.methodType(void.class, Object.class),
        lookup.unreflect(m),
        MethodType.methodType(void.class, Foo.class));
Consumer<Foo> func = (Consumer<Foo>) site.getTarget().invoke();
func.accept(foo);  // IllegalAccessException: member is private

显然m.setAccessible(true)这里还不够。我尝试更改lookupMethodHandles.privateLookupIn(Foo.class, MethodHandles.lookup()),这在我的玩具示例中确实解决了它......但在我的实际应用程序中没有,它会生成一个IllegalAccessException说法,我的班级“没有完全特权访问”。我一直无法发现为什么我的应用程序“没有完全权限访问”,或者如何修复它。

我发现几乎可以工作的唯一一件事是:

MethodHandles.Lookup original = MethodHandles.lookup();
Field internal = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
internal.setAccessible(true);
TRUSTED = (MethodHandles.Lookup) internal.get(original);

只要我在VM选项中有我可以做到TRUSTED的,它就允许我使用它。这会产生一个代替(说它找不到),这似乎很有希望......但我仍然无法弄清楚如何让它完全工作,只是产生这个错误而不是其他错误。lookup--illegal-access=permitNoClassDefFoundErrorFoo

这里发生了什么,我该如何bar访问LambdaMetaFactory

4

1 回答 1

3

我想,您只是setAccessible(true)在玩具示例中尝试了您的方法,而不是实际应用程序。在您的情况下,这些操作的规则之间的差异很小。

Method.setAccessible(boolean)

如果满足以下任何条件,则类中的调用者可以使用此方法C来启用对声明类成员的访问D

  • C并且D在同一个模块中。
  • 该成员现在public并且Dpublic一个包中,包含该模块D的模块至少导出到包含C.
  • 成员是protected static,Dpublic在包含模块的包中D导出到至少包含模块的C, 并且C是 的子类D
  • D位于包含模块的包中D,至少对包含 . 的模块开放C。未命名和打开模块中的所有包都对所有模块开放,因此当D位于未命名或打开模块中时,此方法总是成功。

MethodHandles.privateLookupIn(…)

当且仅当满足以下所有条件时,允许在模块中指定为Lookup对象的调用者对目标类的模块和包进行深度反射:M1M2true

[...]

  • 如果调用者模块M1与目标模块不同,M2则以下两个都必须为真:
    • M1读取M2
    • M2至少打开包含目标类的包M1

privateLookupIn(…)具有比 更严格的规则setAccessible(true),这并不奇怪,因为它比启用对特定单个成员的访问具有更大的影响。由于有问题的成员是private,因此有效应用于操作的点数没有区别。如果访问者在同一个模块中或成员的包已打开到调用者的模块,则授予访问权限。(由于您Foo直接访问调用者代码中的类,读取边缘显然已经存在。)

在您的玩具示例中,这些类可能在同一个模块中或根本不使用模块(在运行时放置在未命名的模块中)。相反,应用程序代码试图访问另一个模块的成员。由于您的访问方法IMPL_LOOKUP通过使用 option 确实有效--illegal-access=permit,因此调用者代码必须位于未命名模块中,而目标必须位于不同模块中,而不是将成员的包打开到未命名模块。

这个实现特定的查找对象是特殊的。它具有 trust 标志,允许它访问所有内容,但它的查找类是java.lang.Object,因此它只能看到引导加载程序可见的类。您必须使用in;更改查找类 与普通查找对象不同,这不会清除权限。

Field internal = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
internal.setAccessible(true);
TRUSTED = (MethodHandles.Lookup) internal.get(null);

MethodHandles.Lookup lookup = TRUSTED.in(Foo.class);
MethodHandle mh = lookup.findSpecial(
    Foo.class, "bar", MethodType.methodType(void.class), Foo.class);

// mh.invokeExact(foo); // could simply invoke it here

CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
        MethodType.methodType(Consumer.class),
        mh.type().erase(), mh, mh.type());
Consumer<Foo> func = (Consumer<Foo>) site.getTarget().invoke();

func.accept(foo);

但需要强调的是,这个 hack 只适用于这个特定的实现,甚至有望在未来的版本中停止工作。实现访问的唯一干净方法是正确设置模块,即添加适当的opens指令。

于 2021-09-06T14:22:21.323 回答