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