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