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