MethodType runtime API vs constant pool entry of the same type
David Holmes
david.holmes at oracle.com
Fri May 13 03:19:38 UTC 2022
On 12/05/2022 2:45 am, Paul Sandoz wrote:
> 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.
Yes please - at the moment I'd say this is a bug: resolution has a very
specific meaning.
> Paul.
>
> [1] ClassLoader::loadClass also confusingly states:
>
> "It is invoked by the Java virtual machine to resolve class references."
Probably should say "when resolving class references". If the virtual
machine is resolving a reference to a class B, in a class A, then it has
to invoke loadClass to load B.
Cheers,
David
>> 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