Java MethodHandle.invokeExact 的奇怪行为

问题描述 投票:0回答:1

这是一个最小的工作示例(需要 Java 22 或更高版本):

(仅使用 libc

free
来阐述行为)

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Main {
    public static final Linker nativeLinker = Linker.nativeLinker();
    public static final SymbolLookup stdlibLookup = nativeLinker.defaultLookup();
    public static final SymbolLookup loaderLookup = SymbolLookup.loaderLookup();

    private static final FunctionDescriptor DESCRIPTOR$free = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS);
    private static final MethodHandle HANDLE$free =
            loaderLookup.find("free")
                    .or(() -> stdlibLookup.find("free"))
                    .map(symbolSegment -> nativeLinker.downcallHandle(symbolSegment, DESCRIPTOR$free))
                    .orElseThrow(() -> new RuntimeException("libc function free not found but y?"));

    public static void free(MemorySegment address) {
        try {
            // I want to allow user code to use Java null and MemorySegment.NULL (C NULL) interchangeably
            HANDLE$free.invokeExact(address != null ? address : MemorySegment.NULL);
        } catch (Throwable throwable) {
            throwable.printStackTrace(System.err);
        }
    }

    public static void main(String[] args) {
        free(null); // free(MemorySegment.NULL) will produce same result
    }
}

运行程序会出现异常:

java.lang.invoke.WrongMethodTypeException: handle's method type (MemorySegment)void but found (Object)void
    at java.base/java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:521)
    at java.base/java.lang.invoke.Invokers.checkExactType(Invokers.java:530)
    at Main.free(Main.java:19)
    at Main.main(Main.java:26)

Java 抱怨用于调用

invokeExact
的参数是
Object
,而不是
MemorySegment
。然而,表达

address != null ? address : MemorySegment.NULL

当然应该有类型

MemorySegment
。如果我们对代码做一点小小的修改:

    public static void free(MemorySegment address) {
        try {
            MemorySegment temp = address != null ? address : MemorySegment.NULL;
            HANDLE$free.invokeExact(temp);
        } catch (Throwable throwable) {
            throwable.printStackTrace(System.err);
        }
    }

它工作正常。

还有

  • HANDLE$free.invoke(address != null ? address : MemorySegment.NULL)
    有效
  • HANDLE$free.invokeExact((MemorySegment)(address != null ? address : MemorySegment.NULL))
    有效,但是
  • HANDLE$free.invokeExact((address != null ? address : MemorySegment.NULL))
    不起作用

这是某种故意设计、实现限制还是 JVM 错误?

java methodhandle project-panama java-22
1个回答
1
投票

请注意,由于

invokeExact
是签名多态的,因此编译器的工作是根据参数表达式确定您尝试调用的方法的方法类型。如果编译器发现不匹配的方法类型,则表达式在运行时计算为什么类型并不重要。

确实,条件表达式(三元运算符表达式)的类型是

Object

来自 JLS 15.25.3,

如果引用条件表达式出现在赋值上下文或调用上下文中,则它是聚合表达式(第 5.2 节。第 5.3 节)。否则,它是一个独立的表达式。

...

多引用条件表达式的类型与其目标类型相同。

如果

HANDLE$free.invokeExact(address != null ? address : MemorySegment.NULL);

条件运算符的目标类型是

Object
,因为
invokeExact
被声明为采用
Object...

如果您在将表达式传递给

MemorySegment
之前将其分配给
invokeExact
类型的变量,则参数表达式实际上就是
MemorySegment
类型的名称表达式。

© www.soinside.com 2019 - 2024. All rights reserved.