1 /*
2  * Copyright (c) 2015, 2018, 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 
loadClass(String name)41     public synchronized Class loadClass(String name) throws ClassNotFoundException {
42         return loadClass(name, false);
43     }
44 
loadClass(String name, boolean resolve)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      */
GeneratingClassLoader(String templateClassName)69     public GeneratingClassLoader(String templateClassName) {
70         this.templateClassName = templateClassName;
71         classPath = System.getProperty("java.class.path").split(File.pathSeparator);
72         try {
73             templateClassNameBytes = templateClassName.getBytes(encoding);
74         } catch (UnsupportedEncodingException e) {
75             throw new RuntimeException(e);
76         }
77     }
78 
79     /**
80      * Create generating class loader that will use class file for
81      * nsk.share.classload.TemplateClass as template.
82      */
GeneratingClassLoader()83     public GeneratingClassLoader() {
84         this(TemplateClass.class.getName());
85     }
86 
getNameLength()87     public int getNameLength() {
88         return templateClassName.length();
89     }
90 
getPrefix()91     String getPrefix() {
92         return PREFIX;
93     }
94 
getClassName(int number)95     public String getClassName(int number) {
96         StringBuffer sb = new StringBuffer();
97         sb.append(PREFIX);
98         sb.append(number);
99         int n = templateClassName.length() - sb.length();
100         for (int i = 0; i < n; ++i) {
101             sb.append("_");
102         }
103         return sb.toString();
104     }
105 
getPatchedByteCode(String name)106     private byte[] getPatchedByteCode(String name) throws ClassNotFoundException {
107         try {
108             byte[] bytecode = getByteCode();
109             String fname = name.replace(".", File.separator);
110             byte[] replaceBytes = fname.getBytes(encoding);
111             for (int offset : offsets) {
112                 for (int i = 0; i < replaceBytes.length; ++i) {
113                     bytecode[offset + i] = replaceBytes[i];
114                 }
115             }
116             return bytecode;
117         } catch (UnsupportedEncodingException e) {
118             throw new RuntimeException(e);
119         }
120     }
121 
getByteCode()122     private byte[] getByteCode() throws ClassNotFoundException {
123         if (bytecode == null) {
124             readByteCode();
125         }
126         if (offsets == null) {
127             getOffsets(bytecode);
128             if (offsets == null) {
129                 throw new RuntimeException("Class name not found in template class file");
130             }
131         }
132         return (byte[]) bytecode.clone();
133     }
134 
readByteCode()135     private void readByteCode() throws ClassNotFoundException {
136         String fname = templateClassName.replace(".", File.separator) + ".class";
137         File target = null;
138         for (int i = 0; i < classPath.length; ++i) {
139             target = new File(classPath[i] + File.separator + fname);
140             if (target.exists()) {
141                 break;
142             }
143         }
144 
145         if (target == null || !target.exists()) {
146             throw new ClassNotFoundException("File not found: " + target);
147         }
148         try {
149             bytecode = ClassLoadUtils.readFile(target);
150         } catch (IOException e) {
151             throw new ClassNotFoundException(templateClassName, e);
152         }
153     }
154 
getOffsets(byte[] bytecode)155     private void getOffsets(byte[] bytecode) {
156         List<Integer> offsets = new ArrayList<Integer>();
157         if (this.offsets == null) {
158             String pname = templateClassName.replace(".", "/");
159             try {
160                 byte[] pnameb = pname.getBytes(encoding);
161                 int i = 0;
162                 while (true) {
163                     while (i < bytecode.length) {
164                         int j = 0;
165                         while (j < pnameb.length && bytecode[i + j] == pnameb[j]) {
166                             ++j;
167                         }
168                         if (j == pnameb.length) {
169                             break;
170                         }
171                         i++;
172                     }
173                     if (i == bytecode.length) {
174                         break;
175                     }
176                     offsets.add(new Integer(i));
177                     i++;
178                 }
179             } catch (UnsupportedEncodingException e) {
180                 throw new RuntimeException(e);
181             }
182             this.offsets = new int[offsets.size()];
183             for (int i = 0; i < offsets.size(); ++i) {
184                 this.offsets[i] = offsets.get(i).intValue();
185             }
186         }
187     }
188 
189     public static final String DEFAULT_CLASSNAME = TemplateClass.class.getName();
190     static final String PREFIX = "Class";
191 
192     private final String[] classPath;
193     private byte[] bytecode;
194     private int[] offsets;
195     private final String encoding = "UTF8";
196     private final String templateClassName;
197     private final byte[] templateClassNameBytes;
198 }
199