MethodType runtime API vs constant pool entry of the same type

Paul Sandoz paul.sandoz at oracle.com
Tue May 10 22:51:49 UTC 2022


MethodType.fromMethodDescriptorString does not have the bytecode behavior of loading a method type from the constant pool, where access control checks are applied.

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