1 /*
2 * Copyright (c) 2015, 2025, 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 package jdk.test.lib.classloader;
24
25 import java.io.*;
26 import java.util.*;
27
28 /**
29 * Classloader that generates classes on the fly.
30 *
31 * This classloader can load classes with name starting with 'Class'. It will
32 * use TemplateClass as template and will replace class name in the bytecode of
33 * template class. It can be used for example to detect memory leaks in class
34 * loading or to quickly fill Metaspace.
35 */
36 class TemplateClass {
37 }
38
39 public class GeneratingClassLoader extends ClassLoader {
40
41 public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
42 return loadClass(name, false);
43 }
44
45 public synchronized Class<?> loadClass(String name, boolean resolve)
46 throws ClassNotFoundException {
47 Class<?> c = findLoadedClass(name);
48 if (c != null) {
49 return c;
50 }
51 if (!name.startsWith(PREFIX)) {
52 return super.loadClass(name, resolve);
53 }
54 if (name.length() != templateClassName.length()) {
55 throw new ClassNotFoundException("Only can load classes with name.length() = " + getNameLength() + " got: '" + name + "' length: " + name.length());
56 }
57 byte[] bytecode = getPatchedByteCode(name);
58 c = defineClass(name, bytecode, 0, bytecode.length);
59 if (resolve) {
60 resolveClass(c);
61 }
62 return c;
63 }
64
65 /**
66 * Create generating class loader that will use class file for given class
67 * from classpath as template.
68 */
69 @SuppressWarnings("initialization")
70 public GeneratingClassLoader(String templateClassName) {
71 this.templateClassName = templateClassName;
72 classPath = System.getProperty("java.class.path").split(File.pathSeparator);
73 try {
74 templateClassNameBytes = templateClassName.getBytes(encoding);
75 } catch (UnsupportedEncodingException e) {
76 throw new RuntimeException(e);
77 }
78 }
79
80 /**
81 * Create generating class loader that will use class file for
82 * nsk.share.classload.TemplateClass as template.
83 */
84 public GeneratingClassLoader() {
85 this(TemplateClass.class.getName());
86 }
87
88 public int getNameLength() {
89 return templateClassName.length();
90 }
91
92 String getPrefix() {
93 return PREFIX;
94 }
95
96 public String getClassName(int number) {
97 StringBuffer sb = new StringBuffer();
98 sb.append(PREFIX);
99 sb.append(number);
100 int n = templateClassName.length() - sb.length();
101 for (int i = 0; i < n; ++i) {
102 sb.append("_");
103 }
104 return sb.toString();
105 }
106
107 private byte[] getPatchedByteCode(String name) throws ClassNotFoundException {
108 try {
109 byte[] bytecode = getByteCode();
110 String fname = name.replace(".", File.separator);
111 byte[] replaceBytes = fname.getBytes(encoding);
112 for (int offset : offsets) {
113 for (int i = 0; i < replaceBytes.length; ++i) {
114 bytecode[offset + i] = replaceBytes[i];
115 }
116 }
117 return bytecode;
118 } catch (UnsupportedEncodingException e) {
119 throw new RuntimeException(e);
120 }
121 }
122
123 private byte[] getByteCode() throws ClassNotFoundException {
124 if (bytecode == null) {
125 readByteCode();
126 }
127 if (offsets == null) {
128 getOffsets(bytecode);
129 if (offsets == null) {
130 throw new RuntimeException("Class name not found in template class file");
131 }
132 }
133 return bytecode.clone();
134 }
135
136 private void readByteCode() throws ClassNotFoundException {
137 String fname = templateClassName.replace(".", File.separator) + ".class";
138 File target = null;
139 for (int i = 0; i < classPath.length; ++i) {
140 target = new File(classPath[i] + File.separator + fname);
141 if (target.exists()) {
142 break;
143 }
144 }
145
146 if (target == null || !target.exists()) {
147 throw new ClassNotFoundException("File not found: " + target);
148 }
149 try {
150 bytecode = ClassLoadUtils.readFile(target);
151 } catch (IOException e) {
152 throw new ClassNotFoundException(templateClassName, e);
153 }
154 }
155
156 private void getOffsets(byte[] bytecode) {
157 List<Integer> offsets = new ArrayList<Integer>();
158 if (this.offsets == null) {
159 String pname = templateClassName.replace(".", "/");
160 try {
161 byte[] pnameb = pname.getBytes(encoding);
162 int i = 0;
163 while (true) {
164 while (i < bytecode.length) {
165 int j = 0;
166 while (j < pnameb.length && bytecode[i + j] == pnameb[j]) {
167 ++j;
168 }
169 if (j == pnameb.length) {
170 break;
171 }
172 i++;
173 }
174 if (i == bytecode.length) {
175 break;
176 }
177 offsets.add(i);
178 i++;
179 }
180 } catch (UnsupportedEncodingException e) {
181 throw new RuntimeException(e);
182 }
183 this.offsets = new int[offsets.size()];
184 for (int i = 0; i < offsets.size(); ++i) {
185 this.offsets[i] = offsets.get(i).intValue();
186 }
187 }
188 }
189
190 public static final String DEFAULT_CLASSNAME = TemplateClass.class.getName();
191 static final String PREFIX = "Class";
192
193 private final String[] classPath;
194 private byte[] bytecode;
195 private int[] offsets;
196 private final String encoding = "UTF8";
197 private final String templateClassName;
198 private final byte[] templateClassNameBytes;
199 }