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