MethodType runtime API vs constant pool entry of the same type

David Holmes david.holmes at oracle.com
Wed May 11 01:07:52 UTC 2022


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