When to initialize the method's class for MethodHandles.Lookup.findStatic()?

Cheng Jin jincheng at ca.ibm.com
Thu Mar 17 23:02:11 UTC 2022


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" <jincheng at ca.ibm.com>
> To: "core-libs-dev" <core-libs-dev at 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


More information about the core-libs-dev mailing list