1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 8242013
  27  * @run testng/othervm test.TypeDescriptorTest
  28  * @summary Test TypeDescriptor::descriptorString for hidden classes which
  29  *          cannot be used to produce ConstantDesc via ClassDesc or
  30  *          MethodTypeDesc factory methods
  31  */
  32 
  33 package test;
  34 
  35 import java.io.IOException;
  36 import java.io.UncheckedIOException;
  37 import java.lang.constant.*;
  38 import java.lang.invoke.*;
  39 import java.lang.invoke.MethodHandles.Lookup;
  40 import java.lang.reflect.Array;
  41 import java.nio.file.Files;
  42 import java.nio.file.Paths;
  43 import static java.lang.invoke.MethodType.*;
  44 
  45 import org.testng.annotations.Test;
  46 import org.testng.annotations.DataProvider;
  47 import static org.testng.Assert.*;
  48 
  49 public class TypeDescriptorTest {
  50     private static final Lookup HC_LOOKUP = defineHiddenClass();
  51     private static final Class<?> HC = HC_LOOKUP.lookupClass();
  52     private static Lookup defineHiddenClass() {
  53         String classes = System.getProperty("test.classes");
  54         try {
  55             byte[] bytes = Files.readAllBytes(Paths.get(classes, "test/HiddenClass.class"));
  56             return MethodHandles.lookup().defineHiddenClass(bytes, true);
  57         } catch (IOException e) {
  58             throw new UncheckedIOException(e);
  59         } catch (IllegalAccessException e) {
  60             throw new RuntimeException(e);
  61         }
  62     }
  63 
  64     @DataProvider(name = "constables")
  65     private Object[][] constables() throws Exception {
  66         return new Object[][] {
  67                 new Object[] { HC },
  68                 new Object[] { methodType(HC) },
  69                 new Object[] { methodType(void.class, HC) },
  70                 new Object[] { methodType(void.class, HC, int.class) },
  71                 new Object[] { HC_LOOKUP.findStatic(HC, "m", methodType(void.class)) },
  72                 new Object[] { HC_LOOKUP.findStaticVarHandle(HC, "f", Object.class) }
  73         };
  74     }
  75 
  76     /*
  77      * Hidden classes have no nominal descriptor.
  78      * Constable::describeConstable returns empty optional.
  79      */
  80     @Test(dataProvider = "constables")
  81     public void noNominalDescriptor(Constable constable) {
  82         assertTrue(constable.describeConstable().isEmpty());
  83     }
  84 
  85     /*
  86      * ClassDesc factory methods throws IAE with the name or descriptor string
  87      * from a hidden class
  88      */
  89     @Test
  90     public void testClassDesc() {
  91         try {
  92             ClassDesc.ofDescriptor(HC.descriptorString());
  93             assertFalse(true);
  94         } catch (IllegalArgumentException e) {
  95             System.out.println(e.getClass().getName() + " " + e.getMessage());
  96         }
  97 
  98         try {
  99             ClassDesc.ofDescriptor(HC.getName());
 100             assertFalse(true);
 101         } catch (IllegalArgumentException e) {
 102             System.out.println(e.getClass().getName() + " " + e.getMessage());
 103         }
 104         try {
 105             ClassDesc.of(HC.getPackageName(), HC.getSimpleName());
 106             assertFalse(true);
 107         } catch (IllegalArgumentException e) {
 108             System.out.println(e.getClass().getName() + " " + e.getMessage());
 109         }
 110         try {
 111             ClassDesc.of(HC.getName());
 112             assertFalse(true);
 113         } catch (IllegalArgumentException e) {
 114             System.out.println(e.getClass().getName() + " " + e.getMessage());
 115         }
 116     }
 117 
 118     @DataProvider(name = "typeDescriptors")
 119     private Object[][] typeDescriptors() throws Exception {
 120         Class<?> hcArray = Array.newInstance(HC, 1, 1).getClass();
 121         return new Object[][] {
 122                 new Object[] { HC, "Ltest/HiddenClass.0x[0-9a-f]+;"},
 123                 new Object[] { hcArray, "\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;"},
 124                 new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
 125                 new Object[] { methodType(void.class, HC), "\\(Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
 126                 new Object[] { methodType(void.class, HC, int.class, Object.class), "\\(Ltest/HiddenClass.0x[0-9a-f]+;ILjava/lang/Object;\\)V" }
 127         };
 128     }
 129 
 130     /*
 131      * Hidden classes have no nominal type descriptor
 132      */
 133     @Test(dataProvider = "typeDescriptors")
 134     public void testTypeDescriptor(TypeDescriptor td, String regex) throws Exception {
 135         String desc = td.descriptorString();
 136         System.out.println(desc + " " + desc.matches(regex));
 137         assertTrue(desc.matches(regex));
 138 
 139         if (td instanceof Class) {
 140             try {
 141                 ClassDesc.ofDescriptor(desc);
 142                 assertFalse(true);
 143             } catch (IllegalArgumentException e) {
 144                 System.out.println(e.getClass().getName() + " " + e.getMessage());
 145             }
 146         } else if (td instanceof MethodType) {
 147             try {
 148                 MethodTypeDesc.ofDescriptor(desc);
 149                 assertFalse(true);
 150             } catch (IllegalArgumentException e) {
 151                 System.out.println(e.getClass().getName() + " " + e.getMessage());
 152             }
 153         }
 154     }
 155 
 156     @DataProvider(name = "methodTypes")
 157     private Object[][] methodTypes() throws Exception {
 158         Class<?> hcArray = Array.newInstance(HC, 1, 1).getClass();
 159         return new Object[][] {
 160                 new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
 161                 new Object[] { methodType(void.class, hcArray), "\\(\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
 162                 new Object[] { methodType(void.class, int.class, HC), "\\(ILtest/HiddenClass.0x[0-9a-f]+;\\)V" }
 163         };
 164     }
 165 
 166     /*
 167      * Test MethodType::toMethodDescriptorString with MethodType referencing to hidden class
 168      */
 169     @Test(dataProvider = "methodTypes")
 170     public void testToMethodDescriptorString(MethodType mtype, String regex) throws Exception {
 171         String desc = mtype.toMethodDescriptorString();
 172         assertTrue(desc.matches(regex));
 173 
 174         try {
 175             MethodType.fromMethodDescriptorString(desc, TypeDescriptorTest.class.getClassLoader());
 176         } catch (IllegalArgumentException e) {
 177             System.out.println("not-well formed: " + desc);
 178         }
 179     }
 180 }
 181 
 182 class HiddenClass {
 183     private static final Object f = new Object();
 184     public static void m() {
 185     }
 186 }