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