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: * - needs better documentation
jaroslav@1646: * - needs more security work (for safe delegation)
jaroslav@1646: * - needs a clearer story about error processing
jaroslav@1646: * - patch member references also (use ';' as delimiter char)
jaroslav@1646: * - patch method references to (conforming) method handles
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 extends sun.misc.Unsafe> 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: }