Hi Raffaello,
The code snippet I posted previously was not the original test I verified on
Java 17 (which was generated via asmtools to trigger verification)
Here's the pretty much the test I used:
Test_1.java
import java.lang.invoke.*;
public class Test_1 {
static MethodHandle mh;
static {
try {
mh = MethodHandles.lookup().findStatic(Test_2.class,
"testMethod", MethodType.methodType(int[].class));
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Throwable {
//Test_1.mh.invoke();
}
}
Test_2.jasm
super public class Test_2
version 52:0
{
static Method "<clinit>":"()V"
stack 5 locals 12
{
bipush -2;
istore 7;
iload 7;
sipush 997;
if_icmplt L127;
iconst_0;
istore 8;
L127: stack_frame_type full;
locals_map int, class "[B", class "[B", class "[Ljava/lang/Class;", class "[I",
class "[I", class "[I", int, bogus, bogus, float, float;
iload 8;
istore 8;
return;
}
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
return;
}
static Method testMethod:"()[I"
stack 6 locals 1
{
getstatic Field testArray:"[I";
areturn;
}
} // end Class Test_2
Jdk17/bin/java -jar asmtools.jar jasm Test_2.jasm // create the .class file
of Test_2
Jdk17/bin/java Test_1
java.lang.IllegalAccessException: no such method:
Test_2.testMethod()int[]/invokeStatic
at
java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:972)
at
java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)
at
java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3649)
at
java.base/java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:2588)
at Test_1.<clinit>(Test_1.java:9)
Caused by: java.lang.VerifyError: Inconsistent stackmap frames at branch target
15
Exception Details:
Location:
Test_2.<clinit>()V @15: iload
Reason:
Type top (current frame, locals[0]) is not assignable to integer (stack
map, locals[0])
Current Frame:
bci: @9
flags: { }
locals: { top, top, top, top, top, top, top, integer }
stack: { integer, integer }
Stackmap Frame:
bci: @15
flags: { }
locals: { integer, '[B', '[B', '[Ljava/lang/Class;', '[I', '[I', '[I',
integer, top, top, float, float }
stack: { }
Bytecode:
0000000: 10fe 3607 1507 1103 e5a1 0006 0336 0815
0000010: 0836 08b1
Stackmap Table:
full_frame(@15,{Integer,Object[#11],Object[#11],Object[#20],Object[#5],Object[#5],Object[#5],Integer,Top,Top,Float,Float},{})
at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native
Method)
at
java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1085)
at
java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)
... 3 more
--------
Test_2 was intentionally modified to throw VerifyError if initialized.
As you see above, mh.inovke() was commented out in Test_1.main() but the
Test_2 was still verified in the test.
So it is impossible to verify Test_2 if it is not initialized, which only means
Test_2 is initialized during the lookup.findstatic rather than mh.invoke().
Best Regards
Cheng
----- Original Message -----
From: "Cheng Jin" <jinch...@ca.ibm.com>
To: "core-libs-dev" <core-libs-dev@openjdk.java.net>
Sent: Thursday, March 17, 2022 5:42:57 PM
Subject: When to initialize the method's class for
MethodHandles.Lookup.findStatic()?
Hi there,
The document of
INVALID URI REMOVED
n_java_javase_17_docs_api_java.base_java_lang_invoke_MethodHandles.Loo
kup.html-23findStatic-28java.lang.Class-2Cjava.lang.String-2Cjava.lang
.invoke.MethodType-29&d=DwIFaQ&c=jf_iaSHvJObTbx-siA1ZOg&r=X90f3XIRXAH8
hbNam6bIUlWfF_qUAezL9ue7M7bFuPQ&m=Xt-10pHYoPWnY6dByKowR4yeLtEs7kZkKUgt
bxKUGvM&s=dPAMGMxphLLXT9N4ZdukiNvWyvRPAGkcGCBLTy_sm0c&e=
in the Java API is ambiguous in terms of when to initialize the
method's class as follows (the same description as in other OpenJDK
versions)
If the returned method handle is invoked, the method's class will be
initialized, if it has not already been initialized.
It occurs to me that the method's class should be initialized when
invoking the method handle but OpenJDK actually chooses to do the
initialization in
lookup.findStatic() rather than in mh.invoke() e.g.
import java.lang.invoke.*;
public class Test_1 {
static MethodHandle mh = MethodHandles.lookup().findStatic(Test_2.class,
"testMethod", MethodType.methodType(int.class, int.class)); <-----------
Test_2.class gets initialized and verified.
public static void main(String[] args) throws Throwable {
Test_1.mh.invoke();
}
}
public class Test_2 {
static int testMethod(int value) { return (value + 1); } }
So there should be more clear explanation what is the correct or
expected behaviour at this point and why OpenJDK doesn't comply with
the document to delay the initialization of the method's class to mh.invoke().
Hi,
if by initialization you mean running the static block, then it's a bug.
As far as i remember, the JVMS spec cleanly separate the Linking exceptions
from the Runtime exceptions.
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokestatic
The linking exceptions occurs when calling findStatic() while the runtime
exceptions will appear at runtime when calling invokeExact()/invoke().
Best Regards
Cheng Jin
regards,
Rémi