--- 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: + * * * A hidden class or interface is never an array class, but may be * the element type of an array. In all other respects, the fact that @@ -1065,10 +1073,7 @@ public String getPackageName() { String pn = this.packageName; if (pn == null) { - Class c = this; - while (c.isArray()) { - c = c.getComponentType(); - } + Class c = isArray() ? elementType() : this; if (c.isPrimitive()) { pn = "java.lang"; } else { @@ -1226,6 +1231,20 @@ private final Class componentType; + /* + * Returns the {@code Class} representing the element type of an array class. + * If this class does not represent an array class, then this method returns + * {@code null}. + */ + private Class elementType() { + if (!isArray()) return null; + + Class c = this; + while (c.isArray()) { + c = c.getComponentType(); + } + return c; + } /** * Returns the Java language modifiers for this class or interface, encoded @@ -3023,10 +3042,7 @@ */ private String resolveName(String name) { if (!name.startsWith("/")) { - Class c = this; - while (c.isArray()) { - c = c.getComponentType(); - } + Class c = isArray() ? elementType() : this; String baseName = c.getPackageName(); if (baseName != null && !baseName.isEmpty()) { name = baseName.replace('.', '/') + "/" + name; @@ -4214,13 +4230,52 @@ } /** - * Returns the type descriptor string for this class. - *

- * 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 describeConstable() { - return Optional.of(ClassDesc.ofDescriptor(descriptorString())); + Class c = isArray() ? elementType() : this; + return c.isHidden() ? Optional.empty() + : Optional.of(ClassDesc.ofDescriptor(descriptorString())); } /** --- old/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2020-04-08 14:54:26.000000000 -0700 +++ new/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2020-04-08 14:54:25.000000000 -0700 @@ -1808,9 +1808,13 @@ * as the lookup class of this {@code Lookup}. *

  • Let {@code GN} be the binary name obtained by taking {@code N} * (a binary name encoded in internal form) and replacing ASCII forward slashes with - * ASCII periods. For the instance of {@link java.lang.Class} representing {@code C}, - * {@link Class#getName()} returns the string {@code GN + "/" + }, even though - * this is not a valid binary class or interface name.
  • + * ASCII periods. For the instance of {@link java.lang.Class} representing {@code C}: + * * * * --- 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}. + * *

    * 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 "()"} + * where {@code } is the concatenation of the + * {@linkplain Class#descriptorString() descriptor string} of all parameter types + * and the {@linkplain Class#descriptorString() descriptor string} of the return type. + * This method type cannot be described nominally and no + * {@link java.lang.constant.MethodTypeDesc MethodTypeDesc} can be produced from + * the result string. + * + * @return the descriptor string for this method type * @since 12 + * @jvms 4.3.3 Method Descriptors */ @Override public String descriptorString() { @@ -1178,9 +1198,13 @@ } /** - * Return a nominal descriptor for this instance, if one can be + * Returns a nominal descriptor for this instance, if one can be * constructed, or an empty {@link Optional} if one cannot be. * + *

    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() { + } +}