【问题标题】:LambdaMetaFactory and Private MethodsLambdaMetaFactory 和私有方法
【发布时间】:2021-11-03 03:57:20
【问题描述】:

我想使用 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) 在这里还不够。我尝试将lookup 更改为MethodHandles.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);

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

这里发生了什么,如何通过LambdaMetaFactory 访问bar

【问题讨论】:

  • “没有完全权限访问”-是的,在某些 Java 版本(iirc Java 14)中已更改,所以我猜您在玩具示例中运行的 Java 版本不同,而您的实际应用。

标签: java reflection java-16 methodhandle lambda-metafactory


【解决方案1】:

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

Method.setAccessible(boolean)

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

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

MethodHandles.privateLookupIn(…)

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

[...]

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

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

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

这个实现特定的查找对象是特殊的。它具有可信任的标志,允许它访问所有内容,但它的查找类是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 指令。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-18
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 2011-08-05
    • 1970-01-01
    相关资源
    最近更新 更多