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