1 /*
   2  * Copyright (c) 2019, 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  * @modules java.base/jdk.internal.org.objectweb.asm
  27  *          jdk.compiler
  28  * @library /test/lib
  29  * @build jdk.test.lib.Utils
  30  *        jdk.test.lib.compiler.CompilerUtils
  31  * @run testng/othervm --enable-preview BasicTest
  32  */
  33 
  34 import java.io.File;
  35 import java.io.IOException;
  36 import java.lang.invoke.MethodHandles.Lookup;
  37 
  38 import static java.lang.invoke.MethodHandles.lookup;
  39 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*;
  40 
  41 import java.lang.reflect.Array;
  42 import java.lang.reflect.Method;
  43 import java.nio.charset.StandardCharsets;
  44 import java.nio.file.Files;
  45 import java.nio.file.Path;
  46 import java.nio.file.Paths;
  47 import java.util.Arrays;
  48 import java.util.List;
  49 import java.util.stream.Stream;
  50 
  51 import jdk.internal.org.objectweb.asm.ClassWriter;
  52 import jdk.internal.org.objectweb.asm.Type;
  53 import jdk.test.lib.compiler.CompilerUtils;
  54 import jdk.test.lib.Utils;
  55 
  56 import org.testng.annotations.BeforeTest;
  57 import org.testng.annotations.DataProvider;
  58 import org.testng.annotations.Test;
  59 
  60 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  61 import static org.testng.Assert.*;
  62 
  63 interface HiddenTest {
  64     void test();
  65 }
  66 
  67 public class BasicTest {
  68 
  69     private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src");
  70     private static final Path CLASSES_DIR = Paths.get("classes");
  71     private static final Path CLASSES_10_DIR = Paths.get("classes_10");
  72 
  73     private static byte[] hiddenClassBytes;
  74 
  75     @BeforeTest
  76     static void setup() throws IOException {
  77         compileSources(SRC_DIR, CLASSES_DIR,
  78                 "--enable-preview", "-source", String.valueOf(Runtime.version().feature()));
  79         hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class"));
  80 
  81         // compile with --release 10 with no NestHost and NestMembers attribute
  82         compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10");
  83         compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10");
  84     }
  85 
  86     static void compileSources(Path sourceFile, Path dest, String... options) throws IOException {
  87         Stream<String> ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR);
  88         if (options != null && options.length > 0) {
  89             ops = Stream.concat(ops, Arrays.stream(options));
  90         }
  91         if (!CompilerUtils.compile(sourceFile, dest, ops.toArray(String[]::new))) {
  92             throw new RuntimeException("Compilation of the test failed: " + sourceFile);
  93         }
  94     }
  95 
  96     static Class<?> defineHiddenClass(String name) throws Exception {
  97         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class"));
  98         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
  99         assertHiddenClass(hc);
 100         singletonNest(hc);
 101         return hc;
 102     }
 103 
 104     // basic test on a hidden class
 105     @Test
 106     public void hiddenClass() throws Throwable {
 107         HiddenTest t = (HiddenTest)defineHiddenClass("HiddenClass").newInstance();
 108         t.test();
 109 
 110         // sanity check
 111         Class<?> c = t.getClass();
 112         Class<?>[] intfs = c.getInterfaces();
 113         assertTrue(c.isHidden());
 114         assertFalse(c.isPrimitive());
 115         assertTrue(intfs.length == 1);
 116         assertTrue(intfs[0] == HiddenTest.class);
 117         assertTrue(c.getCanonicalName() == null);
 118 
 119         String hcName = "HiddenClass";
 120         String hcSuffix = "0x[0-9a-f]+";
 121         assertTrue(c.getName().matches(hcName + "/" + hcSuffix));
 122         assertTrue(c.descriptorString().matches("L" + hcName + ";" + "/" + hcSuffix));
 123 
 124         // test array of hidden class
 125         testHiddenArray(c);
 126 
 127         // test setAccessible
 128         checkSetAccessible(c, "realTest");
 129         checkSetAccessible(c, "test");
 130     }
 131 
 132     // primitive class is not a hidden class
 133     @Test
 134     public void primitiveClass() {
 135         assertFalse(int.class.isHidden());
 136         assertFalse(String.class.isHidden());
 137     }
 138 
 139     private void testHiddenArray(Class<?> type) throws Exception {
 140         // array of hidden class
 141         Object array = Array.newInstance(type, 2);
 142         Class<?> arrayType = array.getClass();
 143         assertTrue(arrayType.isArray());
 144         assertTrue(Array.getLength(array) == 2);
 145         assertFalse(arrayType.isHidden());
 146 
 147         String hcName = "HiddenClass";
 148         String hcSuffix = "0x[0-9a-f]+";
 149         assertTrue(arrayType.getName().matches("\\[" + "L" + hcName + "/" + hcSuffix + ";"));
 150         assertTrue(arrayType.descriptorString().matches("\\[" + "L" + hcName + ";" + "/" + hcSuffix));
 151 
 152         assertTrue(arrayType.getComponentType().isHidden());
 153         assertTrue(arrayType.getComponentType() == type);
 154         Object t = type.newInstance();
 155         Array.set(array, 0, t);
 156         Object o = Array.get(array, 0);
 157         assertTrue(o == t);
 158     }
 159 
 160     private void checkSetAccessible(Class<?> c, String name, Class<?>... ptypes) throws Exception {
 161         Method m = c.getDeclaredMethod(name, ptypes);
 162         assertTrue(m.trySetAccessible());
 163         m.setAccessible(true);
 164     }
 165 
 166     // Define a hidden class that uses lambda
 167     // This verifies LambdaMetaFactory supports the caller which is a hidden class
 168     @Test
 169     public void testLambda() throws Throwable {
 170         HiddenTest t = (HiddenTest)defineHiddenClass("Lambda").newInstance();
 171         try {
 172             t.test();
 173         } catch (Error e) {
 174             if (!e.getMessage().equals("thrown by " + t.getClass().getName())) {
 175                 throw e;
 176             }
 177         }
 178     }
 179 
 180     // Verify the nest host and nest members of a hidden class and hidden nestmate class
 181     @Test
 182     public void testHiddenNestHost() throws Throwable {
 183         byte[] hc1 = hiddenClassBytes;
 184         Lookup lookup1 = lookup().defineHiddenClass(hc1, false);
 185         Class<?> host = lookup1.lookupClass();
 186 
 187         byte[] hc2 = Files.readAllBytes(CLASSES_DIR.resolve("Lambda.class"));
 188         Lookup lookup2 = lookup1.defineHiddenClass(hc2, false, NESTMATE);
 189         Class<?> member = lookup2.lookupClass();
 190 
 191         // test nest membership and reflection API
 192         assertTrue(host.isNestmateOf(member));
 193         assertTrue(host.getNestHost() == host);
 194         // getNestHost and getNestMembers return the same value when calling
 195         // on a nest member and the nest host
 196         assertTrue(member.getNestHost() == host.getNestHost());
 197         assertTrue(Arrays.equals(member.getNestMembers(), host.getNestMembers()));
 198         // getNestMembers includes the nest host that can be a hidden class but
 199         // only includes static nest members
 200         assertTrue(host.getNestMembers().length == 1);
 201         assertTrue(host.getNestMembers()[0] == host);
 202     }
 203 
 204     @DataProvider(name = "hiddenClasses")
 205     private Object[][] hiddenClasses() {
 206         return new Object[][] {
 207                 new Object[] { "HiddenInterface", false },
 208                 new Object[] { "AbstractClass", false },
 209                 // a hidden annotation is useless because it cannot be referenced by any class
 210                 new Object[] { "HiddenAnnotation", false },
 211                 // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute
 212                 // define them as nestmate to verify Class::getNestHost and getNestMembers
 213                 new Object[] { "Outer", true },
 214                 new Object[] { "Outer$Inner", true },
 215                 new Object[] { "EnclosingClass", true },
 216                 new Object[] { "EnclosingClass$1", true },
 217         };
 218     }
 219 
 220     /*
 221      * Test that class file bytes that can be defined as a normal class
 222      * can be successfully created as a hidden class even it might not
 223      * make sense as a hidden class.  For example, a hidden annotation
 224      * is not useful as it cannot be referenced and an outer/inner class
 225      * when defined as a hidden effectively becomes a final top-level class.
 226      */
 227     @Test(dataProvider = "hiddenClasses")
 228     public void defineHiddenClass(String name, boolean nestmate) throws Exception {
 229         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class"));
 230         Class<?> hc;
 231         Class<?> host;
 232         if (nestmate) {
 233             hc = lookup().defineHiddenClass(bytes, false, NESTMATE).lookupClass();
 234             host = lookup().lookupClass().getNestHost();
 235         } else {
 236             hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 237             host = hc;
 238         }
 239         assertTrue(hc.getNestHost() == host);
 240         assertTrue(hc.getNestMembers().length == 1);
 241         assertTrue(hc.getNestMembers()[0] == host);
 242     }
 243 
 244     @DataProvider(name = "emptyClasses")
 245     private Object[][] emptyClasses() {
 246         return new Object[][] {
 247                 new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC },
 248                 new Object[] { "EmptyHiddenEnum", ACC_ENUM },
 249                 new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT },
 250                 new Object[] { "EmptyHiddenInterface", ACC_ABSTRACT|ACC_INTERFACE },
 251                 new Object[] { "EmptyHiddenAnnotation", ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE },
 252         };
 253     }
 254 
 255     /*
 256      * Test if an empty class with valid access flags can be created as a hidden class
 257      * as long as it does not violate the restriction of a hidden class.
 258      *
 259      * A meaningful enum type defines constants of that enum type.  So
 260      * enum class containing constants of its type should not be a hidden
 261      * class.
 262      */
 263     @Test(dataProvider = "emptyClasses")
 264     public void emptyHiddenClass(String name, int accessFlags) throws Exception {
 265         byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, Enum.class, accessFlags)
 266                                                  : classBytes(name, accessFlags);
 267         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 268         switch (accessFlags) {
 269             case ACC_SYNTHETIC:
 270                 assertTrue(hc.isSynthetic());
 271                 assertFalse(hc.isEnum());
 272                 assertFalse(hc.isAnnotation());
 273                 assertFalse(hc.isInterface());
 274                 break;
 275             case ACC_ENUM:
 276                 assertFalse(hc.isSynthetic());
 277                 assertTrue(hc.isEnum());
 278                 assertFalse(hc.isAnnotation());
 279                 assertFalse(hc.isInterface());
 280                 break;
 281             case ACC_ABSTRACT:
 282                 assertFalse(hc.isSynthetic());
 283                 assertFalse(hc.isEnum());
 284                 assertFalse(hc.isAnnotation());
 285                 assertFalse(hc.isInterface());
 286                 break;
 287             case ACC_ABSTRACT|ACC_INTERFACE:
 288                 assertFalse(hc.isSynthetic());
 289                 assertFalse(hc.isEnum());
 290                 assertFalse(hc.isAnnotation());
 291                 assertTrue(hc.isInterface());
 292                 break;
 293             case ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE:
 294                 assertFalse(hc.isSynthetic());
 295                 assertFalse(hc.isEnum());
 296                 assertTrue(hc.isAnnotation());
 297                 assertTrue(hc.isInterface());
 298                 break;
 299             default:
 300                 throw new IllegalArgumentException("unexpected access flag: " + accessFlags);
 301         }
 302         assertTrue(hc.isHidden());
 303         assertTrue(hc.getModifiers() == (ACC_PUBLIC|accessFlags));
 304         assertFalse(hc.isLocalClass());
 305         assertFalse(hc.isMemberClass());
 306         assertFalse(hc.isAnonymousClass());
 307         assertFalse(hc.isArray());
 308     }
 309 
 310     // These class files can't be defined as hidden classes
 311     @DataProvider(name = "cantBeHiddenClasses")
 312     private Object[][] cantBeHiddenClasses() {
 313         return new Object[][] {
 314                 // a hidden class can't be a field's declaring type
 315                 // enum class with static final HiddenEnum[] $VALUES:
 316                 new Object[] { "HiddenEnum" },
 317                 // supertype of this class is a hidden class
 318                 new Object[] { "HiddenSuper" },
 319                 // a record class whose equals(HiddenRecord, Object) method
 320                 // refers to a hidden class in the parameter type and fails
 321                 // verification.  Perhaps this method signature should be reconsidered. 
 322                 new Object[] { "HiddenRecord" },
 323         };
 324     }
 325 
 326     /*
 327      * These class files
 328      */
 329     @Test(dataProvider = "cantBeHiddenClasses", expectedExceptions = NoClassDefFoundError.class)
 330     public void failToDeriveAsHiddenClass(String name) throws Exception {
 331         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class"));
 332         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 333     }
 334 
 335     /*
 336      * A hidden class can be successfully created but fails to be reflected
 337      * if it refers to its own type in the descriptor.
 338      * e.g. Class::getMethods resolves the declaring type of fields,
 339      * parameter types and return type.
 340      */
 341     @Test
 342     public void hiddenCantReflect() throws Throwable {
 343         HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance();
 344         t.test();
 345 
 346         Class<?> c = t.getClass();
 347         Class<?>[] intfs = c.getInterfaces();
 348         assertTrue(intfs.length == 1);
 349         assertTrue(intfs[0] == HiddenTest.class);
 350 
 351         try {
 352             // this would cause loading of class HiddenCantReflect and NCDFE due
 353             // to error during verification
 354             c.getDeclaredMethods();
 355         } catch (NoClassDefFoundError e) {
 356             Throwable x = e.getCause();
 357             if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) {
 358                 throw e;
 359             }
 360         }
 361     }
 362 
 363     @Test(expectedExceptions = {IllegalArgumentException.class})
 364     public void cantDefineModule() throws Throwable {
 365         Path src = Paths.get("module-info.java");
 366         Path dir = CLASSES_DIR.resolve("m");
 367         Files.write(src, List.of("module m {}"), StandardCharsets.UTF_8);
 368         compileSources(src, dir);
 369 
 370         byte[] bytes = Files.readAllBytes(dir.resolve("module-info.class"));
 371         lookup().defineHiddenClass(bytes, false);
 372     }
 373 
 374     @Test(expectedExceptions = {IllegalArgumentException.class})
 375     public void cantDefineClassInAnotherPackage() throws Throwable {
 376         Path src = Paths.get("ClassInAnotherPackage.java");
 377         Files.write(src, List.of("package p;", "public class ClassInAnotherPackage {}"), StandardCharsets.UTF_8);
 378         compileSources(src, CLASSES_DIR);
 379 
 380         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("p").resolve("ClassInAnotherPackage.class"));
 381         lookup().defineHiddenClass(bytes, false);
 382     }
 383 
 384     @Test(expectedExceptions = {IllegalAccessException.class})
 385     public void lessPrivilegedLookup() throws Throwable {
 386         Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE);
 387         lookup.defineHiddenClass(hiddenClassBytes, false);
 388     }
 389 
 390     @DataProvider(name = "nestedTypesOrAnonymousClass")
 391     private Object[][] nestedTypesOrAnonymousClass() {
 392         return new Object[][] {
 393                 // class file with bad InnerClasses or EnclosingMethod attribute
 394                 new Object[] { "Outer", null },
 395                 new Object[] { "Outer$Inner", "Outer" },
 396                 new Object[] { "EnclosingClass", null },
 397                 new Object[] { "EnclosingClass$1", "EnclosingClass" },
 398         };
 399     }
 400 
 401     @Test(dataProvider = "nestedTypesOrAnonymousClass")
 402     public void hasInnerClassesOrEnclosingMethodAttribute(String className, String badDeclaringClassName) throws Throwable {
 403         byte[] bytes = Files.readAllBytes(CLASSES_10_DIR.resolve(className + ".class"));
 404         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 405         hiddenClassWithBadAttribute(hc, badDeclaringClassName);
 406     }
 407 
 408     // define a hidden class with static nest membership
 409     @Test
 410     public void hasStaticNestHost() throws Exception {
 411         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer$Inner.class"));
 412         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 413         hiddenClassWithBadAttribute(hc, "Outer");
 414     }
 415 
 416     @Test
 417     public void hasStaticNestMembers() throws Throwable {
 418         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer.class"));
 419         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 420         assertHiddenClass(hc);
 421         assertTrue(hc.getNestHost() == hc);
 422         Class<?>[] members = hc.getNestMembers();
 423         assertTrue(members.length == 1 && members[0] == hc);
 424     }
 425 
 426     // a hidden class with bad InnerClasses or EnclosingMethod attribute
 427     private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName) {
 428         assertTrue(hc.isHidden());
 429         assertTrue(hc.getCanonicalName() == null);
 430         assertTrue(hc.getName().contains("/"));
 431 
 432         if (badDeclaringClassName == null) {
 433             // the following reflection API assumes a good name in InnerClasses
 434             // or EnclosingMethod attribute can successfully be resolved.
 435             assertTrue(hc.getSimpleName().length() > 0);
 436             assertFalse(hc.isAnonymousClass());
 437             assertFalse(hc.isLocalClass());
 438             assertFalse(hc.isMemberClass());
 439         } else {
 440             declaringClassNotFound(hc, badDeclaringClassName);
 441         }
 442 
 443         // validation of nest membership
 444         assertTrue(hc.getNestHost() == hc);
 445         // validate the static nest membership
 446         Class<?>[] members = hc.getNestMembers();
 447         assertTrue(members.length == 1 && members[0] == hc);
 448     }
 449 
 450     // Class::getSimpleName, Class::isMemberClass
 451     private void declaringClassNotFound(Class<?> c, String cn) {
 452         try {
 453             // fail to find declaring/enclosing class
 454             c.isMemberClass();
 455             assertTrue(false);
 456         } catch (NoClassDefFoundError e) {
 457             if (!e.getMessage().equals(cn)) {
 458                 throw e;
 459             }
 460         }
 461         try {
 462             // fail to find declaring/enclosing class
 463             c.getSimpleName();
 464             assertTrue(false);
 465         } catch (NoClassDefFoundError e) {
 466             if (!e.getMessage().equals(cn)) {
 467                 throw e;
 468             }
 469         }
 470     }
 471 
 472     private static void singletonNest(Class<?> hc) {
 473         assertTrue(hc.getNestHost() == hc);
 474         assertTrue(hc.getNestMembers().length == 1);
 475         assertTrue(hc.getNestMembers()[0] == hc);
 476     }
 477 
 478     private static void assertHiddenClass(Class<?> hc) {
 479         assertTrue(hc.isHidden());
 480         assertTrue(hc.getCanonicalName() == null);
 481         assertTrue(hc.getName().contains("/"));
 482         assertFalse(hc.isAnonymousClass());
 483         assertFalse(hc.isLocalClass());
 484         assertFalse(hc.isMemberClass());
 485         assertFalse(hc.getSimpleName().isEmpty()); // sanity check
 486     }
 487 
 488     private static byte[] classBytes(String classname, int accessFlags) {
 489         return classBytes(classname, Object.class, accessFlags);
 490     }
 491 
 492     private static byte[] classBytes(String classname, Class<?> supertType, int accessFlags) {
 493         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
 494         cw.visit(V14, ACC_PUBLIC|accessFlags, classname, null, Type.getInternalName(supertType), null);
 495         cw.visitEnd();
 496 
 497         return cw.toByteArray();
 498     }
 499 }