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