jaroslav@1646
|
1 |
/*
|
jaroslav@1646
|
2 |
* Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
|
jaroslav@1646
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
jaroslav@1646
|
4 |
*
|
jaroslav@1646
|
5 |
* This code is free software; you can redistribute it and/or modify it
|
jaroslav@1646
|
6 |
* under the terms of the GNU General Public License version 2 only, as
|
jaroslav@1646
|
7 |
* published by the Free Software Foundation. Oracle designates this
|
jaroslav@1646
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
jaroslav@1646
|
9 |
* by Oracle in the LICENSE file that accompanied this code.
|
jaroslav@1646
|
10 |
*
|
jaroslav@1646
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT
|
jaroslav@1646
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
jaroslav@1646
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
jaroslav@1646
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that
|
jaroslav@1646
|
15 |
* accompanied this code).
|
jaroslav@1646
|
16 |
*
|
jaroslav@1646
|
17 |
* You should have received a copy of the GNU General Public License version
|
jaroslav@1646
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation,
|
jaroslav@1646
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
jaroslav@1646
|
20 |
*
|
jaroslav@1646
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
jaroslav@1646
|
22 |
* or visit www.oracle.com if you need additional information or have any
|
jaroslav@1646
|
23 |
* questions.
|
jaroslav@1646
|
24 |
*/
|
jaroslav@1646
|
25 |
|
jaroslav@1646
|
26 |
package sun.invoke.anon;
|
jaroslav@1646
|
27 |
|
jaroslav@1646
|
28 |
import java.io.IOException;
|
jaroslav@1646
|
29 |
import java.lang.reflect.InvocationTargetException;
|
jaroslav@1646
|
30 |
import java.lang.reflect.Method;
|
jaroslav@1646
|
31 |
import sun.misc.IOUtils;
|
jaroslav@1646
|
32 |
|
jaroslav@1646
|
33 |
/**
|
jaroslav@1646
|
34 |
* Anonymous class loader. Will load any valid classfile, producing
|
jaroslav@1646
|
35 |
* a {@link Class} metaobject, without installing that class in the
|
jaroslav@1646
|
36 |
* system dictionary. Therefore, {@link Class#forName(String)} will never
|
jaroslav@1646
|
37 |
* produce a reference to an anonymous class.
|
jaroslav@1646
|
38 |
* <p>
|
jaroslav@1646
|
39 |
* The access permissions of the anonymous class are borrowed from
|
jaroslav@1646
|
40 |
* a <em>host class</em>. The new class behaves as if it were an
|
jaroslav@1646
|
41 |
* inner class of the host class. It can access the host's private
|
jaroslav@1646
|
42 |
* members, if the creator of the class loader has permission to
|
jaroslav@1646
|
43 |
* do so (or to create accessible reflective objects).
|
jaroslav@1646
|
44 |
* <p>
|
jaroslav@1646
|
45 |
* When the anonymous class is loaded, elements of its constant pool
|
jaroslav@1646
|
46 |
* can be patched to new values. This provides a hook to pre-resolve
|
jaroslav@1646
|
47 |
* named classes in the constant pool to other classes, including
|
jaroslav@1646
|
48 |
* anonymous ones. Also, string constants can be pre-resolved to
|
jaroslav@1646
|
49 |
* any reference. (The verifier treats non-string, non-class reference
|
jaroslav@1646
|
50 |
* constants as plain objects.)
|
jaroslav@1646
|
51 |
* <p>
|
jaroslav@1646
|
52 |
* Why include the patching function? It makes some use cases much easier.
|
jaroslav@1646
|
53 |
* Second, the constant pool needed some internal patching anyway,
|
jaroslav@1646
|
54 |
* to anonymize the loaded class itself. Finally, if you are going
|
jaroslav@1646
|
55 |
* to use this seriously, you'll want to build anonymous classes
|
jaroslav@1646
|
56 |
* on top of pre-existing anonymous classes, and that requires patching.
|
jaroslav@1646
|
57 |
*
|
jaroslav@1646
|
58 |
* <p>%%% TO-DO:
|
jaroslav@1646
|
59 |
* <ul>
|
jaroslav@1646
|
60 |
* <li>needs better documentation</li>
|
jaroslav@1646
|
61 |
* <li>needs more security work (for safe delegation)</li>
|
jaroslav@1646
|
62 |
* <li>needs a clearer story about error processing</li>
|
jaroslav@1646
|
63 |
* <li>patch member references also (use ';' as delimiter char)</li>
|
jaroslav@1646
|
64 |
* <li>patch method references to (conforming) method handles</li>
|
jaroslav@1646
|
65 |
* </ul>
|
jaroslav@1646
|
66 |
*
|
jaroslav@1646
|
67 |
* @author jrose
|
jaroslav@1646
|
68 |
* @author Remi Forax
|
jaroslav@1646
|
69 |
* @see <a href="http://blogs.sun.com/jrose/entry/anonymous_classes_in_the_vm">
|
jaroslav@1646
|
70 |
* http://blogs.sun.com/jrose/entry/anonymous_classes_in_the_vm</a>
|
jaroslav@1646
|
71 |
*/
|
jaroslav@1646
|
72 |
|
jaroslav@1646
|
73 |
public class AnonymousClassLoader {
|
jaroslav@1646
|
74 |
final Class<?> hostClass;
|
jaroslav@1646
|
75 |
|
jaroslav@1646
|
76 |
// Privileged constructor.
|
jaroslav@1646
|
77 |
private AnonymousClassLoader(Class<?> hostClass) {
|
jaroslav@1646
|
78 |
this.hostClass = hostClass;
|
jaroslav@1646
|
79 |
}
|
jaroslav@1646
|
80 |
|
jaroslav@1646
|
81 |
public static AnonymousClassLoader make(sun.misc.Unsafe unsafe, Class<?> hostClass) {
|
jaroslav@1646
|
82 |
if (unsafe == null) throw new NullPointerException();
|
jaroslav@1646
|
83 |
return new AnonymousClassLoader(hostClass);
|
jaroslav@1646
|
84 |
}
|
jaroslav@1646
|
85 |
|
jaroslav@1646
|
86 |
public Class<?> loadClass(byte[] classFile) {
|
jaroslav@1646
|
87 |
if (defineAnonymousClass == null) {
|
jaroslav@1646
|
88 |
// no JVM support; try to fake an approximation
|
jaroslav@1646
|
89 |
try {
|
jaroslav@1646
|
90 |
return fakeLoadClass(new ConstantPoolParser(classFile).createPatch());
|
jaroslav@1646
|
91 |
} catch (InvalidConstantPoolFormatException ee) {
|
jaroslav@1646
|
92 |
throw new IllegalArgumentException(ee);
|
jaroslav@1646
|
93 |
}
|
jaroslav@1646
|
94 |
}
|
jaroslav@1646
|
95 |
return loadClass(classFile, null);
|
jaroslav@1646
|
96 |
}
|
jaroslav@1646
|
97 |
|
jaroslav@1646
|
98 |
public Class<?> loadClass(ConstantPoolPatch classPatch) {
|
jaroslav@1646
|
99 |
if (defineAnonymousClass == null) {
|
jaroslav@1646
|
100 |
// no JVM support; try to fake an approximation
|
jaroslav@1646
|
101 |
return fakeLoadClass(classPatch);
|
jaroslav@1646
|
102 |
}
|
jaroslav@1646
|
103 |
Object[] patches = classPatch.patchArray;
|
jaroslav@1646
|
104 |
// Convert class names (this late in the game)
|
jaroslav@1646
|
105 |
// to use slash '/' instead of dot '.'.
|
jaroslav@1646
|
106 |
// Java likes dots, but the JVM likes slashes.
|
jaroslav@1646
|
107 |
for (int i = 0; i < patches.length; i++) {
|
jaroslav@1646
|
108 |
Object value = patches[i];
|
jaroslav@1646
|
109 |
if (value != null) {
|
jaroslav@1646
|
110 |
byte tag = classPatch.getTag(i);
|
jaroslav@1646
|
111 |
switch (tag) {
|
jaroslav@1646
|
112 |
case ConstantPoolVisitor.CONSTANT_Class:
|
jaroslav@1646
|
113 |
if (value instanceof String) {
|
jaroslav@1646
|
114 |
if (patches == classPatch.patchArray)
|
jaroslav@1646
|
115 |
patches = patches.clone();
|
jaroslav@1646
|
116 |
patches[i] = ((String)value).replace('.', '/');
|
jaroslav@1646
|
117 |
}
|
jaroslav@1646
|
118 |
break;
|
jaroslav@1646
|
119 |
case ConstantPoolVisitor.CONSTANT_Fieldref:
|
jaroslav@1646
|
120 |
case ConstantPoolVisitor.CONSTANT_Methodref:
|
jaroslav@1646
|
121 |
case ConstantPoolVisitor.CONSTANT_InterfaceMethodref:
|
jaroslav@1646
|
122 |
case ConstantPoolVisitor.CONSTANT_NameAndType:
|
jaroslav@1646
|
123 |
// When/if the JVM supports these patches,
|
jaroslav@1646
|
124 |
// we'll probably need to reformat them also.
|
jaroslav@1646
|
125 |
// Meanwhile, let the class loader create the error.
|
jaroslav@1646
|
126 |
break;
|
jaroslav@1646
|
127 |
}
|
jaroslav@1646
|
128 |
}
|
jaroslav@1646
|
129 |
}
|
jaroslav@1646
|
130 |
return loadClass(classPatch.outer.classFile, classPatch.patchArray);
|
jaroslav@1646
|
131 |
}
|
jaroslav@1646
|
132 |
|
jaroslav@1646
|
133 |
private Class<?> loadClass(byte[] classFile, Object[] patchArray) {
|
jaroslav@1646
|
134 |
try {
|
jaroslav@1646
|
135 |
return (Class<?>)
|
jaroslav@1646
|
136 |
defineAnonymousClass.invoke(unsafe,
|
jaroslav@1646
|
137 |
hostClass, classFile, patchArray);
|
jaroslav@1646
|
138 |
} catch (Exception ex) {
|
jaroslav@1646
|
139 |
throwReflectedException(ex);
|
jaroslav@1646
|
140 |
throw new RuntimeException("error loading into "+hostClass, ex);
|
jaroslav@1646
|
141 |
}
|
jaroslav@1646
|
142 |
}
|
jaroslav@1646
|
143 |
|
jaroslav@1646
|
144 |
private static void throwReflectedException(Exception ex) {
|
jaroslav@1646
|
145 |
if (ex instanceof InvocationTargetException) {
|
jaroslav@1646
|
146 |
Throwable tex = ((InvocationTargetException)ex).getTargetException();
|
jaroslav@1646
|
147 |
if (tex instanceof Error)
|
jaroslav@1646
|
148 |
throw (Error) tex;
|
jaroslav@1646
|
149 |
ex = (Exception) tex;
|
jaroslav@1646
|
150 |
}
|
jaroslav@1646
|
151 |
if (ex instanceof RuntimeException) {
|
jaroslav@1646
|
152 |
throw (RuntimeException) ex;
|
jaroslav@1646
|
153 |
}
|
jaroslav@1646
|
154 |
}
|
jaroslav@1646
|
155 |
|
jaroslav@1646
|
156 |
private Class<?> fakeLoadClass(ConstantPoolPatch classPatch) {
|
jaroslav@1646
|
157 |
// Implementation:
|
jaroslav@1646
|
158 |
// 1. Make up a new name nobody has used yet.
|
jaroslav@1646
|
159 |
// 2. Inspect the tail-header of the class to find the this_class index.
|
jaroslav@1646
|
160 |
// 3. Patch the CONSTANT_Class for this_class to the new name.
|
jaroslav@1646
|
161 |
// 4. Add other CP entries required by (e.g.) string patches.
|
jaroslav@1646
|
162 |
// 5. Flatten Class constants down to their names, making sure that
|
jaroslav@1646
|
163 |
// the host class loader can pick them up again accurately.
|
jaroslav@1646
|
164 |
// 6. Generate the edited class file bytes.
|
jaroslav@1646
|
165 |
//
|
jaroslav@1646
|
166 |
// Potential limitations:
|
jaroslav@1646
|
167 |
// * The class won't be truly anonymous, and may interfere with others.
|
jaroslav@1646
|
168 |
// * Flattened class constants might not work, because of loader issues.
|
jaroslav@1646
|
169 |
// * Pseudo-string constants will not flatten down to real strings.
|
jaroslav@1646
|
170 |
// * Method handles will (of course) fail to flatten to linkage strings.
|
jaroslav@1646
|
171 |
if (true) throw new UnsupportedOperationException("NYI");
|
jaroslav@1646
|
172 |
Object[] cpArray;
|
jaroslav@1646
|
173 |
try {
|
jaroslav@1646
|
174 |
cpArray = classPatch.getOriginalCP();
|
jaroslav@1646
|
175 |
} catch (InvalidConstantPoolFormatException ex) {
|
jaroslav@1646
|
176 |
throw new RuntimeException(ex);
|
jaroslav@1646
|
177 |
}
|
jaroslav@1646
|
178 |
int thisClassIndex = classPatch.getParser().getThisClassIndex();
|
jaroslav@1646
|
179 |
String thisClassName = (String) cpArray[thisClassIndex];
|
jaroslav@1646
|
180 |
synchronized (AnonymousClassLoader.class) {
|
jaroslav@1646
|
181 |
thisClassName = thisClassName+"\\|"+(++fakeNameCounter);
|
jaroslav@1646
|
182 |
}
|
jaroslav@1646
|
183 |
classPatch.putUTF8(thisClassIndex, thisClassName);
|
jaroslav@1646
|
184 |
byte[] classFile = null;
|
jaroslav@1646
|
185 |
return unsafe.defineClass(null, classFile, 0, classFile.length,
|
jaroslav@1646
|
186 |
hostClass.getClassLoader(),
|
jaroslav@1646
|
187 |
hostClass.getProtectionDomain());
|
jaroslav@1646
|
188 |
}
|
jaroslav@1646
|
189 |
private static int fakeNameCounter = 99999;
|
jaroslav@1646
|
190 |
|
jaroslav@1646
|
191 |
// ignore two warnings on this line:
|
jaroslav@1646
|
192 |
private static sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
|
jaroslav@1646
|
193 |
// preceding line requires that this class be on the boot class path
|
jaroslav@1646
|
194 |
|
jaroslav@1646
|
195 |
static private final Method defineAnonymousClass;
|
jaroslav@1646
|
196 |
static {
|
jaroslav@1646
|
197 |
Method dac = null;
|
jaroslav@1646
|
198 |
Class<? extends sun.misc.Unsafe> unsafeClass = unsafe.getClass();
|
jaroslav@1646
|
199 |
try {
|
jaroslav@1646
|
200 |
dac = unsafeClass.getMethod("defineAnonymousClass",
|
jaroslav@1646
|
201 |
Class.class,
|
jaroslav@1646
|
202 |
byte[].class,
|
jaroslav@1646
|
203 |
Object[].class);
|
jaroslav@1646
|
204 |
} catch (Exception ee) {
|
jaroslav@1646
|
205 |
dac = null;
|
jaroslav@1646
|
206 |
}
|
jaroslav@1646
|
207 |
defineAnonymousClass = dac;
|
jaroslav@1646
|
208 |
}
|
jaroslav@1646
|
209 |
|
jaroslav@1646
|
210 |
private static void noJVMSupport() {
|
jaroslav@1646
|
211 |
throw new UnsupportedOperationException("no JVM support for anonymous classes");
|
jaroslav@1646
|
212 |
}
|
jaroslav@1646
|
213 |
|
jaroslav@1646
|
214 |
|
jaroslav@1646
|
215 |
private static native Class<?> loadClassInternal(Class<?> hostClass,
|
jaroslav@1646
|
216 |
byte[] classFile,
|
jaroslav@1646
|
217 |
Object[] patchArray);
|
jaroslav@1646
|
218 |
|
jaroslav@1646
|
219 |
public static byte[] readClassFile(Class<?> templateClass) throws IOException {
|
jaroslav@1646
|
220 |
String templateName = templateClass.getName();
|
jaroslav@1646
|
221 |
int lastDot = templateName.lastIndexOf('.');
|
jaroslav@1646
|
222 |
java.net.URL url = templateClass.getResource(templateName.substring(lastDot+1)+".class");
|
jaroslav@1646
|
223 |
java.net.URLConnection connection = url.openConnection();
|
jaroslav@1646
|
224 |
int contentLength = connection.getContentLength();
|
jaroslav@1646
|
225 |
if (contentLength < 0)
|
jaroslav@1646
|
226 |
throw new IOException("invalid content length "+contentLength);
|
jaroslav@1646
|
227 |
|
jaroslav@1646
|
228 |
return IOUtils.readFully(connection.getInputStream(), contentLength, true);
|
jaroslav@1646
|
229 |
}
|
jaroslav@1646
|
230 |
}
|