1 /*
  2  * Copyright (c) 2015, 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 /* @test
 25  * @bug 8057919
 26  * @summary Class.getSimpleName() should work for non-JLS compliant class names
 27  */
 28 
 29 import java.lang.classfile.ClassBuilder;
 30 import java.lang.classfile.ClassFile;
 31 import java.lang.classfile.CodeBuilder;
 32 import java.lang.classfile.attribute.EnclosingMethodAttribute;
 33 import java.lang.classfile.attribute.InnerClassInfo;
 34 import java.lang.classfile.attribute.InnerClassesAttribute;
 35 import java.lang.constant.ClassDesc;
 36 import java.lang.reflect.AccessFlag;
 37 import java.util.Optional;
 38 
 39 import static java.lang.classfile.ClassFile.ACC_PUBLIC;
 40 import static java.lang.classfile.ClassFile.ACC_STATIC;
 41 import static java.lang.constant.ConstantDescs.CD_Object;
 42 import static java.lang.constant.ConstantDescs.INIT_NAME;
 43 import static java.lang.constant.ConstantDescs.MTD_void;
 44 
 45 public class GetSimpleNameTest {
 46     static class NestedClass {}
 47     class InnerClass {}
 48 
 49     static Class<?> f1() {
 50         class LocalClass {}
 51         return LocalClass.class;
 52     }
 53 
 54     public static void main(String[] args) throws Exception {
 55         assertEquals(NestedClass.class.getSimpleName(), "NestedClass");
 56         assertEquals( InnerClass.class.getSimpleName(),  "InnerClass");
 57         assertEquals(             f1().getSimpleName(),  "LocalClass");
 58 
 59         java.io.Serializable anon = new java.io.Serializable() {};
 60         assertEquals(anon.getClass().getSimpleName(), "");
 61 
 62         // Java class names, prepended enclosing class name.
 63         testNested("p.Outer$Nested", "p.Outer", "Nested");
 64         testInner( "p.Outer$Inner",  "p.Inner",  "Inner");
 65         testLocal( "p.Outer$1Local", "p.Outer",  "Local");
 66         testAnon(  "p.Outer$1",      "p.Outer",       "");
 67 
 68         // Non-Java class names, prepended enclosing class name.
 69         testNested("p.$C1$Nested", "p.$C1$", "Nested");
 70         testInner( "p.$C1$Inner",  "p.$C1$",  "Inner");
 71         testLocal( "p.$C1$Local",  "p.$C1$",  "Local");
 72         testAnon(  "p.$C1$1",      "p.$C1$",       "");
 73 
 74         // Non-Java class names, unrelated class names.
 75         testNested("p1.$Nested$", "p2.$C1$", "Nested");
 76         testInner( "p1.$Inner$",  "p2.$C1$",  "Inner");
 77         testLocal( "p1.$Local$",  "p2.$C1$",  "Local");
 78         testAnon(  "p1.$anon$",   "p2.$C1$",       "");
 79     }
 80 
 81     static void testNested(String innerName, String outerName, String simpleName) throws Exception {
 82         BytecodeGenerator bg = new BytecodeGenerator(innerName, outerName, simpleName);
 83         CustomCL cl = new CustomCL(innerName, outerName, bg.getNestedClasses(true), bg.getNestedClasses(false));
 84         assertEquals(cl.loadClass(innerName).getSimpleName(), simpleName);
 85     }
 86 
 87     static void testInner(String innerName, String outerName, String simpleName) throws Exception {
 88         BytecodeGenerator bg = new BytecodeGenerator(innerName, outerName, simpleName);
 89         CustomCL cl = new CustomCL(innerName, outerName, bg.getInnerClasses(true), bg.getInnerClasses(false));
 90         assertEquals(cl.loadClass(innerName).getSimpleName(), simpleName);
 91     }
 92 
 93     static void testLocal(String innerName, String outerName, String simpleName) throws Exception {
 94         BytecodeGenerator bg = new BytecodeGenerator(innerName, outerName, simpleName);
 95         CustomCL cl = new CustomCL(innerName, outerName, bg.getLocalClasses(true), bg.getLocalClasses(false));
 96         assertEquals(cl.loadClass(innerName).getSimpleName(), simpleName);
 97     }
 98 
 99     static void testAnon(String innerName, String outerName, String simpleName) throws Exception {
100         BytecodeGenerator bg = new BytecodeGenerator(innerName, outerName, simpleName);
101         CustomCL cl = new CustomCL(innerName, outerName, bg.getAnonymousClasses(true), bg.getAnonymousClasses(false));
102         assertEquals(cl.loadClass(innerName).getSimpleName(), simpleName);
103     }
104 
105     static void assertEquals(Object o1, Object o2) {
106         if (!java.util.Objects.equals(o1, o2)) {
107             throw new AssertionError(o1 + " != " + o2);
108         }
109     }
110 
111     static class CustomCL extends ClassLoader {
112         final String  innerName;
113         final String  outerName;
114 
115         final byte[] innerClassFile;
116         final byte[] outerClassFile;
117 
118         CustomCL(String innerName, String outerName, byte[] innerClassFile, byte[] outerClassFile) {
119             this.innerName = innerName;
120             this.outerName = outerName;
121             this.innerClassFile = innerClassFile;
122             this.outerClassFile = outerClassFile;
123         }
124         @Override
125         protected Class<?> findClass(String name) throws ClassNotFoundException {
126             if (innerName.equals(name)) {
127                 return defineClass(innerName, innerClassFile, 0, innerClassFile.length);
128             } else if (outerName.equals(name)) {
129                 return defineClass(outerName, outerClassFile, 0, outerClassFile.length);
130             } else {
131                 throw new ClassNotFoundException(name);
132             }
133         }
134     }
135 
136     static class BytecodeGenerator {
137         final ClassDesc innerName;
138         final ClassDesc outerName;
139         final String simpleName;
140 
141         BytecodeGenerator(String innerName, String outerName, String simpleName) {
142             this.innerName = ClassDesc.of(innerName);
143             this.outerName = ClassDesc.of(outerName);
144             this.simpleName = simpleName;
145         }
146 
147         static void makeDefaultCtor(ClassBuilder clb) {
148             clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cb -> {
149                 cb.aload(0);
150                 cb.invokespecial(CD_Object, INIT_NAME, MTD_void);
151                 cb.return_();
152             });
153         }
154 
155         void makeCtxk(ClassBuilder clb, boolean isInner) {
156             if (isInner) {
157                 clb.with(EnclosingMethodAttribute.of(outerName,
158                         Optional.of("f"), Optional.of(MTD_void)));
159             } else {
160                 clb.withMethodBody("f", MTD_void, ACC_PUBLIC | ACC_STATIC,
161                         CodeBuilder::return_);
162             }
163         }
164 
165         byte[] getNestedClasses(boolean isInner) {
166             var name = (isInner ? innerName : outerName);
167             return ClassFile.of().build(name, clb -> {
168                 clb.withSuperclass(CD_Object);
169                 clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
170                 clb.with(InnerClassesAttribute.of(
171                         InnerClassInfo.of(innerName,
172                                 Optional.of(outerName),
173                                 Optional.of(simpleName))));
174                 makeDefaultCtor(clb);
175             });
176         }
177 
178         byte[] getInnerClasses(boolean isInner) {
179             var name = (isInner ? innerName : outerName);
180             return ClassFile.of().build(name, clb -> {
181                 clb.withSuperclass(CD_Object);
182                 clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
183                 clb.with(InnerClassesAttribute.of(
184                         InnerClassInfo.of(innerName,
185                                 Optional.of(outerName),
186                                 Optional.of(simpleName),
187                                 AccessFlag.PUBLIC)));
188                 makeDefaultCtor(clb);
189             });
190         }
191 
192         byte[] getLocalClasses(boolean isInner) {
193             var name = (isInner ? innerName : outerName);
194             return ClassFile.of().build(name, clb -> {
195                 clb.withSuperclass(CD_Object);
196                 clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
197                 clb.with(InnerClassesAttribute.of(
198                         InnerClassInfo.of(innerName,
199                                 Optional.empty(),
200                                 Optional.of(simpleName),
201                                 AccessFlag.PUBLIC, AccessFlag.STATIC)));
202                 makeDefaultCtor(clb);
203                 makeCtxk(clb, isInner);
204             });
205         }
206 
207         byte[] getAnonymousClasses(boolean isInner) {
208             var name = (isInner ? innerName : outerName);
209             return ClassFile.of().build(name, clb -> {
210                 clb.withSuperclass(CD_Object);
211                 clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
212                 clb.with(InnerClassesAttribute.of(
213                         InnerClassInfo.of(innerName,
214                                 Optional.empty(),
215                                 Optional.empty(),
216                                 AccessFlag.PUBLIC, AccessFlag.STATIC)));
217                 makeDefaultCtor(clb);
218                 makeCtxk(clb, isInner);
219             });
220         }
221     }
222 }