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