jaroslav@1646: /* jaroslav@1646: * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. jaroslav@1646: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@1646: * jaroslav@1646: * This code is free software; you can redistribute it and/or modify it jaroslav@1646: * under the terms of the GNU General Public License version 2 only, as jaroslav@1646: * published by the Free Software Foundation. Oracle designates this jaroslav@1646: * particular file as subject to the "Classpath" exception as provided jaroslav@1646: * by Oracle in the LICENSE file that accompanied this code. jaroslav@1646: * jaroslav@1646: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@1646: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@1646: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@1646: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@1646: * accompanied this code). jaroslav@1646: * jaroslav@1646: * You should have received a copy of the GNU General Public License version jaroslav@1646: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@1646: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@1646: * jaroslav@1646: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@1646: * or visit www.oracle.com if you need additional information or have any jaroslav@1646: * questions. jaroslav@1646: */ jaroslav@1646: jaroslav@1646: package sun.invoke.anon; jaroslav@1646: jaroslav@1646: import java.io.IOException; jaroslav@1646: import java.lang.reflect.InvocationTargetException; jaroslav@1646: import java.lang.reflect.Method; jaroslav@1646: import sun.misc.IOUtils; jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Anonymous class loader. Will load any valid classfile, producing jaroslav@1646: * a {@link Class} metaobject, without installing that class in the jaroslav@1646: * system dictionary. Therefore, {@link Class#forName(String)} will never jaroslav@1646: * produce a reference to an anonymous class. jaroslav@1646: *

jaroslav@1646: * The access permissions of the anonymous class are borrowed from jaroslav@1646: * a host class. The new class behaves as if it were an jaroslav@1646: * inner class of the host class. It can access the host's private jaroslav@1646: * members, if the creator of the class loader has permission to jaroslav@1646: * do so (or to create accessible reflective objects). jaroslav@1646: *

jaroslav@1646: * When the anonymous class is loaded, elements of its constant pool jaroslav@1646: * can be patched to new values. This provides a hook to pre-resolve jaroslav@1646: * named classes in the constant pool to other classes, including jaroslav@1646: * anonymous ones. Also, string constants can be pre-resolved to jaroslav@1646: * any reference. (The verifier treats non-string, non-class reference jaroslav@1646: * constants as plain objects.) jaroslav@1646: *

jaroslav@1646: * Why include the patching function? It makes some use cases much easier. jaroslav@1646: * Second, the constant pool needed some internal patching anyway, jaroslav@1646: * to anonymize the loaded class itself. Finally, if you are going jaroslav@1646: * to use this seriously, you'll want to build anonymous classes jaroslav@1646: * on top of pre-existing anonymous classes, and that requires patching. jaroslav@1646: * jaroslav@1646: *

%%% TO-DO: jaroslav@1646: *

jaroslav@1646: * jaroslav@1646: * @author jrose jaroslav@1646: * @author Remi Forax jaroslav@1646: * @see jaroslav@1646: * http://blogs.sun.com/jrose/entry/anonymous_classes_in_the_vm jaroslav@1646: */ jaroslav@1646: jaroslav@1646: public class AnonymousClassLoader { jaroslav@1646: final Class hostClass; jaroslav@1646: jaroslav@1646: // Privileged constructor. jaroslav@1646: private AnonymousClassLoader(Class hostClass) { jaroslav@1646: this.hostClass = hostClass; jaroslav@1646: } jaroslav@1646: jaroslav@1646: public static AnonymousClassLoader make(sun.misc.Unsafe unsafe, Class hostClass) { jaroslav@1646: if (unsafe == null) throw new NullPointerException(); jaroslav@1646: return new AnonymousClassLoader(hostClass); jaroslav@1646: } jaroslav@1646: jaroslav@1646: public Class loadClass(byte[] classFile) { jaroslav@1646: if (defineAnonymousClass == null) { jaroslav@1646: // no JVM support; try to fake an approximation jaroslav@1646: try { jaroslav@1646: return fakeLoadClass(new ConstantPoolParser(classFile).createPatch()); jaroslav@1646: } catch (InvalidConstantPoolFormatException ee) { jaroslav@1646: throw new IllegalArgumentException(ee); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: return loadClass(classFile, null); jaroslav@1646: } jaroslav@1646: jaroslav@1646: public Class loadClass(ConstantPoolPatch classPatch) { jaroslav@1646: if (defineAnonymousClass == null) { jaroslav@1646: // no JVM support; try to fake an approximation jaroslav@1646: return fakeLoadClass(classPatch); jaroslav@1646: } jaroslav@1646: Object[] patches = classPatch.patchArray; jaroslav@1646: // Convert class names (this late in the game) jaroslav@1646: // to use slash '/' instead of dot '.'. jaroslav@1646: // Java likes dots, but the JVM likes slashes. jaroslav@1646: for (int i = 0; i < patches.length; i++) { jaroslav@1646: Object value = patches[i]; jaroslav@1646: if (value != null) { jaroslav@1646: byte tag = classPatch.getTag(i); jaroslav@1646: switch (tag) { jaroslav@1646: case ConstantPoolVisitor.CONSTANT_Class: jaroslav@1646: if (value instanceof String) { jaroslav@1646: if (patches == classPatch.patchArray) jaroslav@1646: patches = patches.clone(); jaroslav@1646: patches[i] = ((String)value).replace('.', '/'); jaroslav@1646: } jaroslav@1646: break; jaroslav@1646: case ConstantPoolVisitor.CONSTANT_Fieldref: jaroslav@1646: case ConstantPoolVisitor.CONSTANT_Methodref: jaroslav@1646: case ConstantPoolVisitor.CONSTANT_InterfaceMethodref: jaroslav@1646: case ConstantPoolVisitor.CONSTANT_NameAndType: jaroslav@1646: // When/if the JVM supports these patches, jaroslav@1646: // we'll probably need to reformat them also. jaroslav@1646: // Meanwhile, let the class loader create the error. jaroslav@1646: break; jaroslav@1646: } jaroslav@1646: } jaroslav@1646: } jaroslav@1646: return loadClass(classPatch.outer.classFile, classPatch.patchArray); jaroslav@1646: } jaroslav@1646: jaroslav@1646: private Class loadClass(byte[] classFile, Object[] patchArray) { jaroslav@1646: try { jaroslav@1646: return (Class) jaroslav@1646: defineAnonymousClass.invoke(unsafe, jaroslav@1646: hostClass, classFile, patchArray); jaroslav@1646: } catch (Exception ex) { jaroslav@1646: throwReflectedException(ex); jaroslav@1646: throw new RuntimeException("error loading into "+hostClass, ex); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: private static void throwReflectedException(Exception ex) { jaroslav@1646: if (ex instanceof InvocationTargetException) { jaroslav@1646: Throwable tex = ((InvocationTargetException)ex).getTargetException(); jaroslav@1646: if (tex instanceof Error) jaroslav@1646: throw (Error) tex; jaroslav@1646: ex = (Exception) tex; jaroslav@1646: } jaroslav@1646: if (ex instanceof RuntimeException) { jaroslav@1646: throw (RuntimeException) ex; jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: private Class fakeLoadClass(ConstantPoolPatch classPatch) { jaroslav@1646: // Implementation: jaroslav@1646: // 1. Make up a new name nobody has used yet. jaroslav@1646: // 2. Inspect the tail-header of the class to find the this_class index. jaroslav@1646: // 3. Patch the CONSTANT_Class for this_class to the new name. jaroslav@1646: // 4. Add other CP entries required by (e.g.) string patches. jaroslav@1646: // 5. Flatten Class constants down to their names, making sure that jaroslav@1646: // the host class loader can pick them up again accurately. jaroslav@1646: // 6. Generate the edited class file bytes. jaroslav@1646: // jaroslav@1646: // Potential limitations: jaroslav@1646: // * The class won't be truly anonymous, and may interfere with others. jaroslav@1646: // * Flattened class constants might not work, because of loader issues. jaroslav@1646: // * Pseudo-string constants will not flatten down to real strings. jaroslav@1646: // * Method handles will (of course) fail to flatten to linkage strings. jaroslav@1646: if (true) throw new UnsupportedOperationException("NYI"); jaroslav@1646: Object[] cpArray; jaroslav@1646: try { jaroslav@1646: cpArray = classPatch.getOriginalCP(); jaroslav@1646: } catch (InvalidConstantPoolFormatException ex) { jaroslav@1646: throw new RuntimeException(ex); jaroslav@1646: } jaroslav@1646: int thisClassIndex = classPatch.getParser().getThisClassIndex(); jaroslav@1646: String thisClassName = (String) cpArray[thisClassIndex]; jaroslav@1646: synchronized (AnonymousClassLoader.class) { jaroslav@1646: thisClassName = thisClassName+"\\|"+(++fakeNameCounter); jaroslav@1646: } jaroslav@1646: classPatch.putUTF8(thisClassIndex, thisClassName); jaroslav@1646: byte[] classFile = null; jaroslav@1646: return unsafe.defineClass(null, classFile, 0, classFile.length, jaroslav@1646: hostClass.getClassLoader(), jaroslav@1646: hostClass.getProtectionDomain()); jaroslav@1646: } jaroslav@1646: private static int fakeNameCounter = 99999; jaroslav@1646: jaroslav@1646: // ignore two warnings on this line: jaroslav@1646: private static sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe(); jaroslav@1646: // preceding line requires that this class be on the boot class path jaroslav@1646: jaroslav@1646: static private final Method defineAnonymousClass; jaroslav@1646: static { jaroslav@1646: Method dac = null; jaroslav@1646: Class unsafeClass = unsafe.getClass(); jaroslav@1646: try { jaroslav@1646: dac = unsafeClass.getMethod("defineAnonymousClass", jaroslav@1646: Class.class, jaroslav@1646: byte[].class, jaroslav@1646: Object[].class); jaroslav@1646: } catch (Exception ee) { jaroslav@1646: dac = null; jaroslav@1646: } jaroslav@1646: defineAnonymousClass = dac; jaroslav@1646: } jaroslav@1646: jaroslav@1646: private static void noJVMSupport() { jaroslav@1646: throw new UnsupportedOperationException("no JVM support for anonymous classes"); jaroslav@1646: } jaroslav@1646: jaroslav@1646: jaroslav@1646: private static native Class loadClassInternal(Class hostClass, jaroslav@1646: byte[] classFile, jaroslav@1646: Object[] patchArray); jaroslav@1646: jaroslav@1646: public static byte[] readClassFile(Class templateClass) throws IOException { jaroslav@1646: String templateName = templateClass.getName(); jaroslav@1646: int lastDot = templateName.lastIndexOf('.'); jaroslav@1646: java.net.URL url = templateClass.getResource(templateName.substring(lastDot+1)+".class"); jaroslav@1646: java.net.URLConnection connection = url.openConnection(); jaroslav@1646: int contentLength = connection.getContentLength(); jaroslav@1646: if (contentLength < 0) jaroslav@1646: throw new IOException("invalid content length "+contentLength); jaroslav@1646: jaroslav@1646: return IOUtils.readFully(connection.getInputStream(), contentLength, true); jaroslav@1646: } jaroslav@1646: }