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 }