MethodType runtime API vs constant pool entry of the same type

Paul Sandoz paul.sandoz at oracle.com
Wed May 11 16:45:22 UTC 2022


Hi David,

Yeah, it's confusing, and in this case I think underspecified. I suspect the resolution in this case refers to operating on the loader via ClassLoader::loadClass [1] (whereby object parameter descriptors are first converted to binary class names)

We could update the API spec to be clearer and link to MethodTypeDesc.

Paul.

[1] ClassLoader::loadClass also confusingly states:

  "It is invoked by the Java virtual machine to resolve class references."

> On May 10, 2022, at 6:07 PM, David Holmes <david.holmes at oracle.com> wrote:
> 
> Hi Paul,
> 
> On 11/05/2022 8:51 am, Paul Sandoz wrote:
>> MethodType.fromMethodDescriptorString does not have the bytecode behavior of loading a method type from the constant pool, where access control checks are applied.
> 
> The API spec states:
> 
> "Any class or interface name embedded in the descriptor string will be resolved by the given loader ..."
> 
> And resolution includes an access control check.
> 
> David
> -----
> 
>> The use of the string descriptor is a convenience and not meant to imply bytecode behavior. The method, as stated, defers resolution of classes to the given class loader. In your Java code example it's equivalent to doing:
>> MethodType m = MethodType.methodType(Class.forName("foo.Foo$Bar", false, Test.class.getClassLoader()));
>> The following paragraph after that which you quote states:
>> * When the JVM materializes a {@code MethodType} from a descriptor string,
>> * all classes named in the descriptor must be accessible, and will be loaded.
>> * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.)
>> * This loading may occur at any time before the {@code MethodType} object is first derived.
>> In jasm try doing a ldc of class Bar and you should also get an illegal access error [1].
>> The equivalent bytecode behavior (except for the exception, error vs runtime exception) can be achieved using the nominal descriptor:
>> MethodTypeDesc d = MethodTypeDesc.ofDescriptor("()Lfoo/Foo$Bar;");
>> MethodType m = (MethodType) d.resolveConstantDesc(MethodHandles.lookup()); // throws IllegalAccessException
>> Which in turn is equivalent to:
>> MethodHandles.Lookup l = MethodHandles.lookup();
>> MethodType m = MethodType.fromMethodDescriptorString(
>>         "()Lfoo/Foo$Bar;",
>>         l.lookupClass().getClassLoader()
>> );
>> l.accessClass(m.returnType()); // <— perform the access check as required for bytecode behavior
>> Hth,
>> Paul.
>> [1] https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-5.html#jvms-5.4.3.1
>>> On May 9, 2022, at 8:08 PM, Maxim Degtyarev <mdegtyarev at gmail.com> wrote:
>>> 
>>> Inspired by https://mail.openjdk.java.net/pipermail/compiler-dev/2022-May/019558.html
>>> 
>>> According to the JDK 17 `j.l.i.MethodType` javadoc [1]:
>>> 
>>> Like classes and strings, method types can also be represented
>>> directly in a class file's constant pool as constants.
>>> A method type may be loaded by an ldc instruction which refers to a
>>> suitable CONSTANT_MethodType constant pool entry.
>>> The entry refers to a CONSTANT_Utf8 spelling for the descriptor
>>> string. (For full details on method type constants,
>>> see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
>>> 
>>> 
>>> If I understand it correctly, this implies that any `MethodType`
>>> instance that can be successfully created with the
>>> call to `MethodType.fromMethodDescriptorString()` method can be also
>>> represented as a constant pool entry of type MethodType.
>>> 
>>> 
>>> Now consider the following example:
>>> 
>>> 
>>> // main/TestRuntime.java
>>> package main;
>>> 
>>> import java.lang.invoke.MethodType;
>>> 
>>> public class TestRuntime {
>>> 
>>>    public static void main(String... args) {
>>>        System.out.println(
>>>            MethodType.fromMethodDescriptorString(
>>>                "()Lfoo/Foo$Bar;",
>>>                TestRuntime.class.getClassLoader()
>>>            )
>>>        );
>>>    }
>>> 
>>> }
>>> 
>>> 
>>> // main/TestConstantPool.jasm
>>> /**
>>> *
>>> * You need <a href="https://wiki.openjdk.java.net/display/CodeTools/asmtools">asmtools</a>
>>> to assemble this example.
>>> *
>>> * To assemble run:
>>> * <code>
>>> *        java -jar asmtools.jar jasm -g TestConstantPool.jasm
>>> * </code>
>>> *
>>> */
>>> package  main;
>>> 
>>> super public class TestConstantPool
>>>    version 52:0
>>> {
>>>  public Method "<init>":"()V"
>>>    stack 1 locals 1
>>>  {
>>>        aload_0;
>>>        invokespecial    Method java/lang/Object."<init>":"()V";
>>>        return;
>>>  }
>>> 
>>>  public static varargs Method main:"([Ljava/lang/String;)V"
>>>    stack 2 locals 1
>>>  {
>>>        getstatic    Field java/lang/System.out:"Ljava/io/PrintStream;";
>>>        ldc MethodType "()Lfoo/Foo$Bar;";
>>>        invokevirtual    Method
>>> java/io/PrintStream.println:"(Ljava/lang/Object;)V";
>>>        return;
>>> 
>>>  }
>>> } // end Class TestConstantPool
>>> 
>>> 
>>> // foo/Foo.java
>>> package foo;
>>> 
>>> public class Foo {
>>>    public static Bar bar() {
>>>        return new Bar();
>>>    }
>>> 
>>>    static class Bar {}
>>> }
>>> 
>>> 
>>> Both `main.TestRuntime` and `main.TestConstantPool` are functionally
>>> equal and should print
>>> string representation of the `MethodType` instance.
>>> 
>>> 
>>> While `main.TestRuntime` behaves as expected:
>>> 
>>> $ java main.TestRuntime
>>> ()Bar
>>> 
>>> 
>>> The `main.TestConstantPool` failed with `j.l.IllegalAccessError`:
>>> 
>>> $ java main.TestConstantPool
>>> Exception in thread "main" java.lang.IllegalAccessError: tried to
>>> access class foo.Foo$Bar from class main.TestConstantPool
>>>        at main.TestConstantPool.main(TestConstantPool.jasm:18)
>>> 
>>> 
>>> However, if we declare class `foo.Foo.Bar` public then both
>>> `TestRuntime` and `TestConstantPool` run without exceptions.
>>> 
>>> 
>>> Checked both with `Java(TM) SE Runtime Environment (build
>>> 1.8.0_152-b16)` and `OpenJDK Runtime Environment (build
>>> 19-ea+21-1482)`.
>>> 
>>> 
>>> Questions:
>>> 
>>> 1) Shouldn't both examples expose equal behaviour?
>>> 
>>> 2) Which of 2 observed behaviours is correct?
>>> 
>>> 
>>> Examples from this message can be found at [2]
>>> 
>>> 
>>> [1] https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/MethodType.html
>>> [2] https://github.com/Maccimo/MethodType-API-vs-ConstantPool



More information about the hotspot-runtime-dev mailing list