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  * @library /test/lib
  27  * @build jdk.test.lib.Utils
  28  *        jdk.test.lib.compiler.CompilerUtils
  29  *        BasicTest
  30  * @run testng/othervm BasicTest
  31  * @run testng/othervm -Xcomp BasicTest
  32  */
  33 
  34 import java.io.File;
  35 import java.io.IOException;
  36 import java.lang.invoke.MethodHandles.Lookup;
  37 import java.lang.invoke.MethodHandles.Lookup.ClassOption;
  38 import static java.lang.invoke.MethodHandles.lookup;
  39 
  40 import java.lang.reflect.Array;
  41 import java.lang.reflect.Method;
  42 import java.nio.charset.StandardCharsets;
  43 import java.nio.file.Files;
  44 import java.nio.file.Path;
  45 import java.nio.file.Paths;
  46 import java.util.Arrays;
  47 import java.util.List;
  48 import java.util.stream.Stream;
  49 
  50 import jdk.test.lib.compiler.CompilerUtils;
  51 import jdk.test.lib.Utils;
  52 
  53 import org.testng.annotations.BeforeTest;
  54 import org.testng.annotations.DataProvider;
  55 import org.testng.annotations.Test;
  56 import static org.testng.Assert.*;
  57 
  58 interface HiddenTest {
  59     void test();
  60 }
  61 
  62 public class BasicTest {
  63 
  64     private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src");
  65     private static final Path CLASSES_DIR = Paths.get("classes");
  66     private static final Path CLASSES_10_DIR = Paths.get("classes_10");
  67 
  68     @BeforeTest
  69     static void setup() throws IOException {
  70         compileSources(SRC_DIR, CLASSES_DIR);
  71 
  72         // compile with --release 10 with no NestHost and NestMembers attribute
  73         compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10");
  74         compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10");
  75     }
  76 
  77     static void compileSources(Path sourceFile, Path dest, String... options) throws IOException {
  78         Stream<String> ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR);
  79         if (options != null && options.length > 0) {
  80             ops = Stream.concat(ops, Arrays.stream(options));
  81         }
  82         if (!CompilerUtils.compile(sourceFile, dest, ops.toArray(String[]::new))) {
  83             throw new RuntimeException("Compilation of the test failed: " + sourceFile);
  84         }
  85     }
  86 
  87     static Class<?> defineHiddenClass(String name) throws Exception {
  88         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class"));
  89         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
  90         assertHiddenClass(hc);
  91         singletonNest(hc);
  92         return hc;
  93     }
  94 
  95     // basic test on a hidden class
  96     @Test
  97     public void hiddenClass() throws Throwable {
  98         HiddenTest t = (HiddenTest)defineHiddenClass("HiddenClass").newInstance();
  99         t.test();
 100 
 101         // sanity check
 102         Class<?> c = t.getClass();
 103         Class<?>[] intfs = c.getInterfaces();
 104         assertTrue(c.isHiddenClass());
 105         assertFalse(c.isPrimitive());
 106         assertTrue(intfs.length == 1);
 107         assertTrue(intfs[0] == HiddenTest.class);
 108         assertTrue(c.getCanonicalName() == null);
 109         assertTrue(c.getName().startsWith("HiddenClass/"));
 110 
 111         // test array of hidden class
 112         testHiddenArray(c);
 113 
 114         // test setAccessible
 115         checkSetAccessible(c, "realTest");
 116         checkSetAccessible(c, "test");
 117     }
 118 
 119     @Test
 120     public void primitiveClass() {
 121         assertFalse(int.class.isHiddenClass());
 122         assertFalse(String.class.isHiddenClass());
 123     }
 124 
 125     private void testHiddenArray(Class<?> type) throws Exception {
 126         // array of hidden class
 127         Object array = Array.newInstance(type, 2);
 128         Class<?> arrayType = array.getClass();
 129         assertTrue(arrayType.isArray());
 130         assertTrue(Array.getLength(array) == 2);
 131         assertFalse(arrayType.isHiddenClass());
 132         assertTrue(arrayType.getName().startsWith("[LHiddenClass/"), "unexpected name: " + arrayType.getName());
 133 
 134         assertTrue(arrayType.getComponentType().isHiddenClass());
 135         assertTrue(arrayType.getComponentType() == type);
 136         Object t = type.newInstance();
 137         Array.set(array, 0, t);
 138         Object o = Array.get(array, 0);
 139         assertTrue(o == t);
 140     }
 141 
 142     private void checkSetAccessible(Class<?> c, String name, Class<?>... ptypes) throws Exception {
 143         Method m = c.getDeclaredMethod(name, ptypes);
 144         assertTrue(m.trySetAccessible());
 145         m.setAccessible(true);
 146     }
 147 
 148     // Define a hidden class that uses lambda
 149     // This verifies LambdaMetaFactory supports the caller which is a hidden class
 150     @Test
 151     public void testLambda() throws Throwable {
 152         HiddenTest t = (HiddenTest)defineHiddenClass("Lambda").newInstance();
 153         try {
 154             t.test();
 155         } catch (Error e) {
 156             if (!e.getMessage().equals("thrown by " + t.getClass().getName())) {
 157                 throw e;
 158             }
 159         }
 160     }
 161 
 162     // Verify the nest host and nest members of a hidden class and hidden nestmate class
 163     @Test
 164     public void testHiddenNestHost() throws Throwable {
 165         byte[] hc1 = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class"));
 166         Lookup lookup1 = lookup().defineHiddenClass(hc1, false);
 167         Class<?> host = lookup1.lookupClass();
 168 
 169         byte[] hc2 = Files.readAllBytes(CLASSES_DIR.resolve("Lambda.class"));
 170         Lookup lookup2 = lookup1.defineHiddenClass(hc2, false, ClassOption.NESTMATE);
 171         Class<?> member = lookup2.lookupClass();
 172 
 173         // test nest membership and reflection API
 174         assertTrue(host.isNestmateOf(member));
 175         assertTrue(host.getNestHost() == host);
 176         // getNestHost and getNestMembers return the same value when calling
 177         // on a nest member and the nest host
 178         assertTrue(member.getNestHost() == host.getNestHost());
 179         assertTrue(Arrays.equals(member.getNestMembers(), host.getNestMembers()));
 180         // getNestMembers includes the nest host that can be a hidden class but
 181         // only includes static nest members
 182         assertTrue(host.getNestMembers().length == 1);
 183         assertTrue(host.getNestMembers()[0] == host);
 184     }
 185 
 186     @Test
 187     public void hiddenCantReflect() throws Throwable {
 188         HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance();
 189         t.test();
 190 
 191         Class<?> c = t.getClass();
 192         Class<?>[] intfs = c.getInterfaces();
 193         assertTrue(intfs.length == 1);
 194         assertTrue(intfs[0] == HiddenTest.class);
 195 
 196         try {
 197             // this would cause loading of class HiddenCantReflect and NCDFE due
 198             // to error during verification
 199             c.getDeclaredMethods();
 200         } catch (NoClassDefFoundError e) {
 201             Throwable x = e.getCause();
 202             if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) {
 203                 throw e;
 204             }
 205         }
 206     }
 207 
 208     @DataProvider(name = "hiddenClasses")
 209     private Object[][] hiddenClasses() {
 210         return new Object[][] {
 211                 new Object[] { "HiddenInterface", false },
 212                 new Object[] { "AbstractClass", false },
 213                 // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute
 214                 // define them as nestmate to verify Class::getNestHost and getNestMembers
 215                 new Object[] { "Outer", true },
 216                 new Object[] { "Outer$Inner", true },
 217                 new Object[] { "EnclosingClass", true },
 218                 new Object[] { "EnclosingClass$1", true },
 219         };
 220     }
 221 
 222     @Test(dataProvider = "hiddenClasses")
 223     public void defineHiddenClass(String name, boolean nestmate) throws Exception {
 224         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class"));
 225         Class<?> hc;
 226         Class<?> host;
 227         if (nestmate) {
 228             hc = lookup().defineHiddenClass(bytes, false, ClassOption.NESTMATE).lookupClass();
 229             host = lookup().lookupClass().getNestHost();
 230         } else {
 231             hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 232             host = hc;
 233         }
 234         assertTrue(hc.getNestHost() == host);
 235         assertTrue(hc.getNestMembers().length == 1);
 236         assertTrue(hc.getNestMembers()[0] == host);
 237     }
 238 
 239     @Test(expectedExceptions = NoClassDefFoundError.class)
 240     public void hiddenSuperClass() throws Exception {
 241         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenSuper.class"));
 242         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 243     }
 244 
 245     @Test(expectedExceptions = {IllegalArgumentException.class})
 246     public void cantDefineModule() throws Throwable {
 247         Path src = Paths.get("module-info.java");
 248         Path dir = CLASSES_DIR.resolve("m");
 249         Files.write(src, List.of("module m {}"), StandardCharsets.UTF_8);
 250         compileSources(src, dir);
 251 
 252         byte[] bytes = Files.readAllBytes(dir.resolve("module-info.class"));
 253         lookup().defineHiddenClass(bytes, false);
 254     }
 255 
 256     @Test(expectedExceptions = {IllegalArgumentException.class})
 257     public void cantDefineClassInAnotherPackage() throws Throwable {
 258         Path src = Paths.get("ClassInAnotherPackage.java");
 259         Files.write(src, List.of("package p;", "public class ClassInAnotherPackage {}"), StandardCharsets.UTF_8);
 260         compileSources(src, CLASSES_DIR);
 261 
 262         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("p").resolve("ClassInAnotherPackage.class"));
 263         lookup().defineHiddenClass(bytes, false);
 264     }
 265 
 266     @Test(expectedExceptions = {IllegalAccessException.class})
 267     public void lessPrivilegedLookup() throws Throwable {
 268         Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE);
 269         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class"));
 270         lookup.defineHiddenClass(bytes, false);
 271     }
 272 
 273     @DataProvider(name = "nestedTypesOrAnonymousClass")
 274     private Object[][] nestedTypesOrAnonymousClass() {
 275         return new Object[][] {
 276                 // class file with bad InnerClasses or EnclosingMethod attribute
 277                 new Object[] { "Outer", null },
 278                 new Object[] { "Outer$Inner", "Outer" },
 279                 new Object[] { "EnclosingClass", null },
 280                 new Object[] { "EnclosingClass$1", "EnclosingClass" },
 281         };
 282     }
 283 
 284     @Test(dataProvider = "nestedTypesOrAnonymousClass")
 285     public void hasInnerClassesOrEnclosingMethodAttribute(String className, String badDeclaringClassName) throws Throwable {
 286         byte[] bytes = Files.readAllBytes(CLASSES_10_DIR.resolve(className + ".class"));
 287         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 288         hiddenClassWithBadAttribute(hc, badDeclaringClassName, null);
 289     }
 290 
 291     private static final String BAD_NEST_HOST_CLASS_ERROR = "Unable to load nest-host class (Outer) of Outer$Inner/";
 292 
 293     // define a hidden class with static nest membership
 294     // it fails when it attempts to validate the nest membership
 295     @Test
 296     public void hasStaticNestHost() throws Exception {
 297         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer$Inner.class"));
 298         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 299         hiddenClassWithBadAttribute(hc, "Outer", BAD_NEST_HOST_CLASS_ERROR);
 300     }
 301 
 302     @Test
 303     public void hasStaticNestMembers() throws Throwable {
 304         byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer.class"));
 305         Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass();
 306         assertHiddenClass(hc);
 307         assertTrue(hc.getNestHost() == hc);
 308         try {
 309             // fail to validate the static nest membership
 310             hc.getNestMembers();
 311             assertTrue(false);
 312         } catch (NoClassDefFoundError e) {
 313             if (!e.getMessage().equals("Outer$Inner")) {
 314                 throw e;
 315             }
 316         }
 317     }
 318 
 319     // a hidden class with bad InnerClasses or EnclosingMethod attribute
 320     private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName, String badNestMembersError) {
 321         assertTrue(hc.isHiddenClass());
 322         assertTrue(hc.getCanonicalName() == null);
 323         assertTrue(hc.getName().contains("/"));
 324 
 325         if (badDeclaringClassName == null) {
 326             // the following reflection API assumes a good name in InnerClasses
 327             // or EnclosingMethod attribute can successfully be resolved.
 328             assertTrue(hc.getSimpleName().length() > 0);
 329             assertFalse(hc.isAnonymousClass());
 330             assertFalse(hc.isLocalClass());
 331             assertFalse(hc.isMemberClass());
 332         } else {
 333             declaringClassNotFound(hc, badDeclaringClassName);
 334         }
 335 
 336         // validation of nest membership may fail
 337         assertTrue(hc.getNestHost() == hc);
 338         try {
 339             // validate the static nest membership
 340             hc.getNestMembers();
 341             assertTrue(badNestMembersError == null);
 342         } catch (NoClassDefFoundError e) {
 343             if (!e.getMessage().startsWith(badNestMembersError)) {
 344                 throw e;
 345             }
 346         }
 347     }
 348 
 349     // Class::getSimpleName, Class::isMemberClass
 350     private void declaringClassNotFound(Class<?> c, String cn) {
 351         try {
 352             // fail to find declaring/enclosing class
 353             c.isMemberClass();
 354             assertTrue(false);
 355         } catch (NoClassDefFoundError e) {
 356             if (!e.getMessage().equals(cn)) {
 357                 throw e;
 358             }
 359         }
 360         try {
 361             // fail to find declaring/enclosing class
 362             c.getSimpleName();
 363             assertTrue(false);
 364         } catch (NoClassDefFoundError e) {
 365             if (!e.getMessage().equals(cn)) {
 366                 throw e;
 367             }
 368         }
 369     }
 370 
 371     private static void singletonNest(Class<?> hc) {
 372         assertTrue(hc.getNestHost() == hc);
 373         assertTrue(hc.getNestMembers().length == 1);
 374         assertTrue(hc.getNestMembers()[0] == hc);
 375     }
 376 
 377     private static void assertHiddenClass(Class<?> hc) {
 378         assertTrue(hc.isHiddenClass());
 379         assertTrue(hc.getCanonicalName() == null);
 380         assertTrue(hc.getName().contains("/"));
 381         assertFalse(hc.isAnonymousClass());
 382         assertFalse(hc.isLocalClass());
 383         assertFalse(hc.isMemberClass());
 384         assertFalse(hc.getSimpleName().isEmpty()); // sanity check
 385     }
 386 }