--- old/src/java.base/share/classes/java/lang/Class.java 2020-04-08 14:54:24.000000000 -0700 +++ new/src/java.base/share/classes/java/lang/Class.java 2020-04-08 14:54:24.000000000 -0700 @@ -163,10 +163,18 @@ * * The {@linkplain #getName() name of a hidden class or interface} is * not a binary name, - * which means that a hidden class or interface cannot be - * referenced by the constant pools of other classes and interfaces, - * and cannot be discovered by {@link #forName Class::forName} or - * {@link ClassLoader#loadClass(String, boolean) ClassLoader::loadClass}. + * which means the following: + *
- * Note that this is not a strict inverse of {@link #forName}; + * Returns the descriptor string of the entity (class, interface, array class, + * primitive type, or {@code void}) represented by this {@code Class} object. + * + *
If this {@code Class} object represents a class or interface, + * not an array class, then: + *
If this {@code Class} object represents an array class, then + * the result is a string consisting of one or more '{@code [}' characters + * representing the depth of the array nesting, followed by the + * descriptor string of the element type. + *
If this {@code Class} object represents a primitive type or
+ * {@code void}, then the result is a field descriptor string which
+ * is a one-letter code corresponding to a primitive type or {@code void}
+ * ({@code "B", "C", "D", "F", "I", "J", "S", "Z", "V"}) (JVMS {@jvms 4.3.2}).
+ *
+ * @apiNote
+ * This is not a strict inverse of {@link #forName};
* distinct classes which share a common name but have different class loaders
* will have identical descriptor strings.
*
- * @return the type descriptor representation
+ * @return the descriptor string for this {@code Class} object
* @jvms 4.3.2 Field Descriptors
* @since 12
*/
@@ -4228,10 +4283,15 @@
public String descriptorString() {
if (isPrimitive())
return Wrapper.forPrimitiveType(this).basicTypeString();
- else if (isArray()) {
+
+ if (isArray()) {
return "[" + componentType.descriptorString();
- }
- else {
+ } else if (isHidden()) {
+ String name = getName();
+ int index = name.indexOf('/');
+ return "L" + name.substring(0, index).replace('.', '/')
+ + "." + name.substring(index+1, name.length()) + ";";
+ } else {
return "L" + getName().replace('.', '/') + ";";
}
}
@@ -4274,7 +4334,9 @@
*/
@Override
public Optional
* Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}.
* Two distinct classes which share a common name but have different class loaders
@@ -1149,7 +1152,8 @@
* generate bytecodes that process method handles and {@code invokedynamic}.
* {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString},
* because the latter requires a suitable class loader argument.
- * @return the bytecode type descriptor representation
+ * @return the descriptor string for this method type
+ * @jvms 4.3.3 Method Descriptors
*/
public String toMethodDescriptorString() {
String desc = methodDescriptor;
@@ -1161,11 +1165,27 @@
}
/**
- * Return a field type descriptor string for this type
+ * Returns a descriptor string for this method type.
+ *
+ * If none of the parameter types and return type is a {@linkplain Class#isHidden()
+ * hidden} class or interface, then the result is a method type descriptor string
+ * (JVMS {@jvms 4.3.3}). {@link MethodTypeDesc MethodTypeDesc} can be created from
+ * the result method type descriptor via
+ * {@link MethodTypeDesc#ofDescriptor(String) MethodTypeDesc::ofDescriptor}.
*
- * @return the descriptor string
- * @jvms 4.3.2 Field Descriptors
+ * If any of the parameter types and return type is a {@linkplain Class#isHidden()
+ * hidden} class or interface, then the result is a string of the form:
+ * {@code "( If any of the parameter types and return type is {@linkplain Class#isHidden()
+ * hidden}, then this method returns an empty {@link Optional} because
+ * this method type cannot be described in nominal form.
+ *
* @return An {@link Optional} containing the resulting nominal descriptor,
* or an empty {@link Optional} if one cannot be constructed.
* @since 12
--- old/src/java.base/share/classes/java/lang/invoke/TypeDescriptor.java 2020-04-08 14:54:28.000000000 -0700
+++ new/src/java.base/share/classes/java/lang/invoke/TypeDescriptor.java 2020-04-08 14:54:28.000000000 -0700
@@ -27,7 +27,7 @@
import java.util.List;
/**
- * An entity that has a field or method type descriptor
+ * An entity that has a type descriptor.
*
* @jvms 4.3.2 Field Descriptors
* @jvms 4.3.3 Method Descriptors
@@ -36,10 +36,19 @@
*/
public interface TypeDescriptor {
/**
- * Return the type descriptor string for this instance, which must be either
- * a field type descriptor (JVMS 4.3.2) or method type descriptor (JVMS 4.3.3).
+ * Return the descriptor string for this {@code TypeDescriptor} object.
*
- * @return the type descriptor
+ * If this {@code TypeDescriptor} object can be described nominally, then
+ * this method returns a field type descriptor (JVMS {@jvms 4.3.2}) or
+ * method type descriptor (JVMS {@jvms 4.3.3}). The result descriptor
+ * can be used to produce a {@linkplain java.lang.constant.ConstantDesc
+ * nominal descriptor}.
+ *
+ * Otherwise, the result string is not a valid type descriptor and
+ * no {@linkplain java.lang.constant.ConstantDesc nominal descriptor}
+ * can be produced from the result string.
+ *
+ * @return the descriptor string for this {@code TypeDescriptor} object
* @jvms 4.3.2 Field Descriptors
* @jvms 4.3.3 Method Descriptors
*/
--- old/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java 2020-04-08 14:54:30.000000000 -0700
+++ new/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java 2020-04-08 14:54:30.000000000 -0700
@@ -154,11 +154,7 @@
} else if (t == Object.class) {
sb.append("Ljava/lang/Object;");
} else {
- boolean lsemi = (!t.isArray());
- if (lsemi) sb.append('L');
- sb.append(t.getName().replace('.', '/'));
- if (lsemi) sb.append(';');
+ sb.append(t.descriptorString());
}
}
-
}
--- /dev/null 2020-04-08 14:54:32.000000000 -0700
+++ new/test/jdk/java/lang/invoke/defineHiddenClass/TypeDescriptorTest.java 2020-04-08 14:54:31.000000000 -0700
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8242013
+ * @run testng/othervm test.TypeDescriptorTest
+ * @summary Test TypeDescriptor::descriptorString for hidden classes which
+ * cannot be used to produce ConstantDesc via ClassDesc or
+ * MethodTypeDesc factory methods
+ */
+
+package test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.constant.*;
+import java.lang.invoke.*;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Array;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import static java.lang.invoke.MethodType.*;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+import static org.testng.Assert.*;
+
+public class TypeDescriptorTest {
+ private static final Lookup HC_LOOKUP = defineHiddenClass();
+ private static final Class> HC = HC_LOOKUP.lookupClass();
+ private static Lookup defineHiddenClass() {
+ String classes = System.getProperty("test.classes");
+ try {
+ byte[] bytes = Files.readAllBytes(Paths.get(classes, "test/HiddenClass.class"));
+ return MethodHandles.lookup().defineHiddenClass(bytes, true);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @DataProvider(name = "constables")
+ private Object[][] constables() throws Exception {
+ return new Object[][] {
+ new Object[] { HC },
+ new Object[] { methodType(HC) },
+ new Object[] { methodType(void.class, HC) },
+ new Object[] { methodType(void.class, HC, int.class) },
+ new Object[] { HC_LOOKUP.findStatic(HC, "m", methodType(void.class)) },
+ new Object[] { HC_LOOKUP.findStaticVarHandle(HC, "f", Object.class) }
+ };
+ }
+
+ /*
+ * Hidden classes have no nominal descriptor.
+ * Constable::describeConstable returns empty optional.
+ */
+ @Test(dataProvider = "constables")
+ public void noNominalDescriptor(Constable constable) {
+ assertTrue(constable.describeConstable().isEmpty());
+ }
+
+ /*
+ * ClassDesc factory methods throws IAE with the name or descriptor string
+ * from a hidden class
+ */
+ @Test
+ public void testClassDesc() {
+ try {
+ ClassDesc.ofDescriptor(HC.descriptorString());
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+
+ try {
+ ClassDesc.ofDescriptor(HC.getName());
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+ try {
+ ClassDesc.of(HC.getPackageName(), HC.getSimpleName());
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+ try {
+ ClassDesc.of(HC.getName());
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+ }
+
+ @DataProvider(name = "typeDescriptors")
+ private Object[][] typeDescriptors() throws Exception {
+ Class> hcArray = Array.newInstance(HC, 1, 1).getClass();
+ return new Object[][] {
+ new Object[] { HC, "Ltest/HiddenClass.0x[0-9a-f]+;"},
+ new Object[] { hcArray, "\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;"},
+ new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
+ new Object[] { methodType(void.class, HC), "\\(Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
+ new Object[] { methodType(void.class, HC, int.class, Object.class), "\\(Ltest/HiddenClass.0x[0-9a-f]+;ILjava/lang/Object;\\)V" }
+ };
+ }
+
+ /*
+ * Hidden classes have no nominal type descriptor
+ */
+ @Test(dataProvider = "typeDescriptors")
+ public void testTypeDescriptor(TypeDescriptor td, String regex) throws Exception {
+ String desc = td.descriptorString();
+ System.out.println(desc + " " + desc.matches(regex));
+ assertTrue(desc.matches(regex));
+
+ if (td instanceof Class) {
+ try {
+ ClassDesc.ofDescriptor(desc);
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+ } else if (td instanceof MethodType) {
+ try {
+ MethodTypeDesc.ofDescriptor(desc);
+ assertFalse(true);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getClass().getName() + " " + e.getMessage());
+ }
+ }
+ }
+
+ @DataProvider(name = "methodTypes")
+ private Object[][] methodTypes() throws Exception {
+ Class> hcArray = Array.newInstance(HC, 1, 1).getClass();
+ return new Object[][] {
+ new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
+ new Object[] { methodType(void.class, hcArray), "\\(\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
+ new Object[] { methodType(void.class, int.class, HC), "\\(ILtest/HiddenClass.0x[0-9a-f]+;\\)V" }
+ };
+ }
+
+ /*
+ * Test MethodType::toMethodDescriptorString with MethodType referencing to hidden class
+ */
+ @Test(dataProvider = "methodTypes")
+ public void testToMethodDescriptorString(MethodType mtype, String regex) throws Exception {
+ String desc = mtype.toMethodDescriptorString();
+ assertTrue(desc.matches(regex));
+
+ try {
+ MethodType.fromMethodDescriptorString(desc, TypeDescriptorTest.class.getClassLoader());
+ } catch (IllegalArgumentException e) {
+ System.out.println("not-well formed: " + desc);
+ }
+ }
+}
+
+class HiddenClass {
+ private static final Object f = new Object();
+ public static void m() {
+ }
+}
+ *
*
*
*
--- old/src/java.base/share/classes/java/lang/invoke/MethodType.java 2020-04-08 14:54:27.000000000 -0700
+++ new/src/java.base/share/classes/java/lang/invoke/MethodType.java 2020-04-08 14:54:27.000000000 -0700
@@ -99,6 +99,7 @@
* 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.
+ *
* @author John Rose, JSR 292 EG
* @since 1.7
*/
@@ -1139,7 +1140,9 @@
}
/**
- * Produces a bytecode descriptor representation of the method type.
+ * Returns a descriptor string for the method type. This method
+ * is equivalent to calling {@link #descriptorString() MethodType::descriptorString}.
+ *
*