All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.instrumentation.engine.weaving.ClassLoaders Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.glowroot.instrumentation.engine.weaving;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import org.glowroot.instrumentation.test.harness.shaded.com.google.common.collect.Maps;
import org.glowroot.instrumentation.test.harness.shaded.com.google.common.collect.Sets;
import org.glowroot.instrumentation.test.harness.shaded.com.google.common.hash.Hasher;
import org.glowroot.instrumentation.test.harness.shaded.com.google.common.hash.Hashing;
import org.glowroot.instrumentation.test.harness.shaded.com.google.common.io.Closer;
import org.glowroot.instrumentation.test.harness.shaded.org.objectweb.asm.Type;
import org.glowroot.instrumentation.test.harness.shaded.org.slf4j.Logger;
import org.glowroot.instrumentation.test.harness.shaded.org.slf4j.LoggerFactory;

class ClassLoaders {

    private static final Logger logger = LoggerFactory.getLogger(ClassLoaders.class);

    private static final Object lock = new Object();

    private ClassLoaders() {}

    static void defineClassesInBootstrapClassLoader(Collection lazyDefinedClasses,
            Instrumentation instrumentation, File tmpDir, String fileNamePrefix)
            throws IOException {

        if (lazyDefinedClasses.isEmpty()) {
            return;
        }
        // using a flattened and ordered lists so that unique hash and file contents will be the
        // same regardless or ordering
        Collection flattenedAndOrderedList =
                getFlattenedAndOrderedList(lazyDefinedClasses);

        String uniqueHash = getUniqueHash(flattenedAndOrderedList);
        File generatedJarFile = new File(tmpDir, fileNamePrefix + uniqueHash + ".jar");
        if (!generatedJarFile.exists()) {
            File tmpFile = createTempFile(fileNamePrefix + uniqueHash, ".jar", tmpDir);
            Closer closer = Closer.create();
            try {
                FileOutputStream out = closer.register(new FileOutputStream(tmpFile));
                JarOutputStream jarOut = closer.register(new JarOutputStream(out));
                generate(flattenedAndOrderedList, jarOut);
            } catch (Throwable t) {
                throw closer.rethrow(t);
            } finally {
                closer.close();
            }
            if (!tmpFile.renameTo(generatedJarFile)) {
                if (!generatedJarFile.exists()) {
                    logger.warn("could not rename file {} to {}", tmpFile.getAbsolutePath(),
                            generatedJarFile.getAbsolutePath());
                    // use tmpFile instead
                    generatedJarFile = tmpFile;
                } else if (!tmpFile.delete()) {
                    logger.warn("could not delete file {}", tmpFile.getAbsolutePath());
                }
            }
        }
        instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(generatedJarFile));
        // appendToBootstrapClassLoaderSearch() line above does not add to the bootstrap resource
        // search path, only to the bootstrap class search path (this is different from
        // appendToSystemClassLoaderSearch() which adds to both the system resource search path and
        // the system class search path)
        //
        // adding the generated jar file to the bootstrap resource search path is probably needed
        // more generally, but it is at least needed to support jboss 4.2.0 - 4.2.3 because
        // org.jboss.mx.loading.LoadMgr3.beginLoadTask() checks that the class loader has the class
        // as a resource before loading it, so without adding the generated jar file to the
        // bootstrap resource search path, jboss ends up throwing ClassNotFoundException for the
        // generated classes that have been added to the bootstrap class loader search path
        // (see issue #101 for more info on this particular jboss issue)
        appendToBootstrapResourcePath(generatedJarFile);
    }

    static void defineClasses(Collection lazyDefinedClasses, ClassLoader loader)
            throws Exception {
        for (LazyDefinedClass lazyDefinedClass : lazyDefinedClasses) {
            defineClass(lazyDefinedClass, loader);
        }
    }

    static void defineClassIfNotExists(LazyDefinedClass lazyDefinedClass, ClassLoader loader)
            throws Exception {
        defineClassIfNotExists(lazyDefinedClass, loader, new HashSet());
    }

    static void defineClass(String name, byte[] bytes, ClassLoader loader) throws Exception {
        Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",
                String.class, byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);
        defineClassMethod.invoke(loader, name, bytes, 0, bytes.length);
    }

    static void createDirectoryOrCleanPreviousContentsWithPrefix(File dir, String prefix)
            throws IOException {
        deleteIfRegularFile(dir);
        if (dir.exists()) {
            deleteFilesWithPrefix(dir, prefix);
        } else {
            createDirectory(dir);
        }
    }

    private static void defineClass(LazyDefinedClass lazyDefinedClass, ClassLoader loader)
            throws Exception {
        for (LazyDefinedClass dependency : lazyDefinedClass.getDependencies()) {
            defineClass(dependency, loader);
        }
        defineClass(lazyDefinedClass.getType().getClassName(), lazyDefinedClass.getBytes(), loader);
    }

    private static void defineClassIfNotExists(LazyDefinedClass lazyDefinedClass,
            ClassLoader loader, Set alreadyDefinedOrToBeDefined) throws Exception {
        if (!alreadyDefinedOrToBeDefined.add(lazyDefinedClass.getType())) {
            // this is to deal with circular references between lazy defined classes since they are
            // now cached/shared across advice
            return;
        }
        for (LazyDefinedClass dependency : lazyDefinedClass.getDependencies()) {
            defineClassIfNotExists(dependency, loader, alreadyDefinedOrToBeDefined);
        }
        String className = lazyDefinedClass.getType().getClassName();
        // synchronized block is needed to guard against race condition (for class loaders that
        // support concurrent class loading), otherwise can have two threads evaluate !classExists,
        // and both try to defineClass, leading to one of them getting java.lang.LinkageError:
        // "attempted duplicate class definition for name"
        //
        // deadlock should not be possible here since ifNotExists is only called from Weaver, and
        // ClassFileTransformers are not re-entrant, so defineClass() should be self contained
        synchronized (lock) {
            if (!classExists(className, loader)) {
                defineClass(className, lazyDefinedClass.getBytes(), loader);
            }
        }
    }

    private static boolean classExists(String name, ClassLoader loader) throws Exception {
        Method findLoadedClassMethod =
                ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
        findLoadedClassMethod.setAccessible(true);
        return findLoadedClassMethod.invoke(loader, name) != null;
    }

    private static void generate(Collection flattenedAndOrderedList,
            JarOutputStream jarOut) throws IOException {
        for (LazyDefinedClass lazyDefinedClass : flattenedAndOrderedList) {
            JarEntry jarEntry =
                    new JarEntry(lazyDefinedClass.getType().getInternalName() + ".class");
            jarOut.putNextEntry(jarEntry);
            jarOut.write(lazyDefinedClass.getBytes());
            jarOut.closeEntry();
        }
    }

    private static void deleteIfRegularFile(File file) throws IOException {
        if (file.isFile() && !file.delete()) {
            logger.debug("could not delete file: " + file.getAbsolutePath());
        }
    }

    private static void deleteFilesWithPrefix(File dir, String prefix) throws IOException {
        File[] files = dir.listFiles();
        if (files == null) {
            // strangely, listFiles() returns null if an I/O error occurs
            throw new IOException("Could not get listing for directory: " + dir.getAbsolutePath());
        }
        for (File file : files) {
            if (file.getName().startsWith(prefix) && !file.delete()) {
                logger.debug("could not delete file: " + file.getAbsolutePath());
            }
        }
    }

    private static void createDirectory(File dir) throws IOException {
        if (!dir.mkdirs()) {
            throw new IOException("Could not create directory: " + dir.getAbsolutePath());
        }
    }

    private static void appendToBootstrapResourcePath(File generatedJarFile) {
        try {
            Class launcherClass = Class.forName("sun.misc.Launcher", false, null);
            Method getBootstrapClassPathMethod = launcherClass.getMethod("getBootstrapClassPath");
            Class urlClassPathClass = Class.forName("sun.misc.URLClassPath", false, null);
            Method addUrlMethod = urlClassPathClass.getMethod("addURL", URL.class);
            Object urlClassPath = getBootstrapClassPathMethod.invoke(null);
            addUrlMethod.invoke(urlClassPath, generatedJarFile.toURI().toURL());
        } catch (Exception e) {
            // NOTE sun.misc.Launcher no longer exists in Java 9
            logger.debug(e.getMessage(), e);
        }
    }

    private static Collection getFlattenedAndOrderedList(
            Collection lazyDefinedClasses) {
        Map flattenedMap = Maps.newTreeMap();
        put(flattenedMap, lazyDefinedClasses);
        return flattenedMap.values();
    }

    private static void put(Map flattenedMap,
            Collection lazyDefinedClasses) {
        for (LazyDefinedClass lazyDefinedClass : lazyDefinedClasses) {
            flattenedMap.put(lazyDefinedClass.getType().getInternalName(), lazyDefinedClass);
            put(flattenedMap, lazyDefinedClass.getDependencies());
        }
    }

    // not using File.createTempFile() because that calls SecureRandom under the hood which can be
    // really slow the first time it's used (and so can have a big impact on startup time)
    private static File createTempFile(String prefix, String suffix, File tmpDir)
            throws IOException {
        String baseName = prefix + "-" + System.currentTimeMillis() + "-";
        for (int i = 0; i < 10000; i++) {
            File tempFile = new File(tmpDir, baseName + i + suffix);
            if (tempFile.createNewFile()) {
                return tempFile;
            }
        }
        throw new IOException("Could not create temp file, tried " + baseName + "0" + suffix
                + " through " + baseName + "999" + suffix);
    }

    private static String getUniqueHash(Collection flattened) {
        Hasher hasher = Hashing.sha1().newHasher();
        for (LazyDefinedClass lazyDefinedClass : flattened) {
            hasher.putBytes(lazyDefinedClass.getBytes());
        }
        return hasher.hash().toString();
    }

    static class LazyDefinedClass {

        private final Type type;
        private final byte[] bytes;
        private final Set dependencies = Sets.newConcurrentHashSet();

        LazyDefinedClass(String internalName, byte[] bytes) {
            this.type = Type.getObjectType(internalName);
            this.bytes = bytes;
        }

        public Type getType() {
            return type;
        }

        public byte[] getBytes() {
            return bytes;
        }

        public Set getDependencies() {
            return dependencies;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy