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         Class<?> hcArray = Array.newInstance(HC, 1).getClass();
  67         return new Object[][] {
  68                 new Object[] { HC },
  69                 new Object[] { hcArray },
  70                 new Object[] { methodType(HC) },
  71                 new Object[] { methodType(void.class, HC) },
  72                 new Object[] { methodType(void.class, HC, int.class) },
  73                 new Object[] { HC_LOOKUP.findStatic(HC, "m", methodType(void.class)) },
  74                 new Object[] { HC_LOOKUP.findStaticVarHandle(HC, "f", Object.class) }
  75         };
  76     }
  77 
  78     /*
  79      * Hidden classes have no nominal descriptor.
  80      * Constable::describeConstable returns empty optional.
  81      */
  82     @Test(dataProvider = "constables")
  83     public void noNominalDescriptor(Constable constable) {
  84         assertTrue(constable.describeConstable().isEmpty());
  85     }
  86 
  87     /*
  88      * ClassDesc factory methods throws IAE with the name or descriptor string
  89      * from a hidden class
  90      */
  91     @Test
  92     public void testClassDesc() {
  93         try {
  94             ClassDesc.ofDescriptor(HC.descriptorString());
  95             assertFalse(true);
  96         } catch (IllegalArgumentException e) {}
  97 
  98         try {
  99             ClassDesc.ofDescriptor(HC.getName());
 100             assertFalse(true);
 101         } catch (IllegalArgumentException e) {}
 102         try {
 103             ClassDesc.of(HC.getPackageName(), HC.getSimpleName());
 104             assertFalse(true);
 105         } catch (IllegalArgumentException e) {}
 106         try {
 107             ClassDesc.of(HC.getName());
 108             assertFalse(true);
 109         } catch (IllegalArgumentException e) {}
 110     }
 111 
 112     @DataProvider(name = "typeDescriptors")
 113     private Object[][] typeDescriptors() throws Exception {
 114         Class<?> hcArray = Array.newInstance(HC, 1, 1).getClass();
 115         return new Object[][] {
 116                 new Object[] { HC, "Ltest/HiddenClass.0x[0-9a-f]+;"},
 117                 new Object[] { hcArray, "\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;"},
 118                 new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
 119                 new Object[] { methodType(void.class, HC), "\\(Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
 120                 new Object[] { methodType(void.class, HC, int.class, Object.class), "\\(Ltest/HiddenClass.0x[0-9a-f]+;ILjava/lang/Object;\\)V" }
 121         };
 122     }
 123 
 124     /*
 125      * Hidden classes have no nominal type descriptor
 126      */
 127     @Test(dataProvider = "typeDescriptors")
 128     public void testTypeDescriptor(TypeDescriptor td, String regex) throws Exception {
 129         String desc = td.descriptorString();
 130         assertTrue(desc.matches(regex));
 131 
 132         if (td instanceof Class) {
 133             try {
 134                 ClassDesc.ofDescriptor(desc);
 135                 assertFalse(true);
 136             } catch (IllegalArgumentException e) {}
 137         } else if (td instanceof MethodType) {
 138             try {
 139                 MethodTypeDesc.ofDescriptor(desc);
 140                 assertFalse(true);
 141             } catch (IllegalArgumentException e) {}
 142         }
 143     }
 144 
 145     @DataProvider(name = "methodTypes")
 146     private Object[][] methodTypes() throws Exception {
 147         Class<?> hcArray = Array.newInstance(HC, 1, 1).getClass();
 148         return new Object[][] {
 149                 new Object[] { methodType(HC), "\\(\\)Ltest/HiddenClass.0x[0-9a-f]+;" },
 150                 new Object[] { methodType(void.class, hcArray), "\\(\\[\\[Ltest/HiddenClass.0x[0-9a-f]+;\\)V" },
 151                 new Object[] { methodType(void.class, int.class, HC), "\\(ILtest/HiddenClass.0x[0-9a-f]+;\\)V" }
 152         };
 153     }
 154 
 155     /*
 156      * Test MethodType::toMethodDescriptorString with MethodType referencing to hidden class
 157      */
 158     @Test(dataProvider = "methodTypes")
 159     public void testToMethodDescriptorString(MethodType mtype, String regex) throws Exception {
 160         String desc = mtype.toMethodDescriptorString();
 161         assertTrue(desc.matches(regex));
 162 
 163         try {
 164             MethodType.fromMethodDescriptorString(desc, TypeDescriptorTest.class.getClassLoader());
 165             assertFalse(true);
 166         } catch (IllegalArgumentException e) {}
 167     }
 168 }
 169 
 170 class HiddenClass {
 171     private static final Object f = new Object();
 172     public static void m() {
 173     }
 174 }