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 }