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

org.glowroot.agent.weaving.WeaverImpl Maven / Gradle / Ivy

There is a newer version: 0.9.24
Show newest version
/*
 * Copyright 2012-2016 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.agent.weaving;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.CodeSource;
import java.util.List;

import javax.annotation.Nullable;

import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.StandardSystemProperty;
import org.glowroot.agent.shaded.google.common.base.Supplier;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.io.Files;
import org.glowroot.agent.shaded.objectweb.asm.ClassReader;
import org.glowroot.agent.shaded.objectweb.asm.ClassVisitor;
import org.glowroot.agent.shaded.objectweb.asm.ClassWriter;
import org.glowroot.agent.shaded.objectweb.asm.MethodVisitor;
import org.glowroot.agent.shaded.objectweb.asm.commons.JSRInlinerAdapter;
import org.glowroot.agent.shaded.objectweb.asm.util.CheckClassAdapter;
import org.glowroot.agent.shaded.slf4j.Logger;
import org.glowroot.agent.shaded.slf4j.LoggerFactory;

import org.glowroot.agent.weaving.AnalyzedWorld.ParseContext;
import org.glowroot.agent.weaving.WeavingTimerService.WeavingTimer;

import static org.glowroot.agent.shaded.objectweb.asm.Opcodes.ASM5;

public class WeaverImpl implements Weaver {

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

    // useful for debugging java.lang.VerifyErrors
    private static final boolean verifyWeaving = Boolean.getBoolean("glowroot.weaving.verify");

    private final Supplier> advisors;
    private final ImmutableList shimTypes;
    private final ImmutableList mixinTypes;
    private final AnalyzedWorld analyzedWorld;
    private final WeavingTimerService weavingTimerService;

    public WeaverImpl(Supplier> advisors, List shimTypes,
            List mixinTypes, AnalyzedWorld analyzedWorld,
            WeavingTimerService weavingTimerService) {
        this.advisors = advisors;
        this.shimTypes = ImmutableList.copyOf(shimTypes);
        this.mixinTypes = ImmutableList.copyOf(mixinTypes);
        this.analyzedWorld = analyzedWorld;
        this.weavingTimerService = weavingTimerService;
    }

    @Override
    public byte /*@Nullable*/[] weave(byte[] classBytes, String className,
            @Nullable CodeSource codeSource, @Nullable ClassLoader loader) {
        WeavingTimer weavingTimer = weavingTimerService.start();
        try {
            logger.trace("transform(): className={}", className);
            byte[] transformedBytes = weaveUnderTimer(classBytes, className, codeSource, loader);
            if (transformedBytes != null) {
                logger.debug("transform(): transformed {}", className);
            }
            return transformedBytes;
        } finally {
            weavingTimer.stop();
        }
    }

    private byte/*@Nullable*/[] weaveUnderTimer(byte[] classBytes, String className,
            @Nullable CodeSource codeSource, @Nullable ClassLoader loader) {
        List advisors = analyzedWorld.mergeInstrumentAnnotations(this.advisors.get(),
                classBytes, loader, className);
        ThinClassVisitor accv = new ThinClassVisitor();
        new ClassReader(classBytes).accept(accv, ClassReader.SKIP_FRAMES + ClassReader.SKIP_CODE);
        byte[] maybeFelixBytes = null;
        if (className.equals("org/apache/felix/framework/BundleWiringImpl")) {
            ClassWriter cw = new ComputeFramesClassWriter(ClassWriter.COMPUTE_FRAMES, analyzedWorld,
                    loader, codeSource, className);
            ClassVisitor cv = new FelixOsgiHackClassVisitor(cw);
            ClassReader cr = new ClassReader(classBytes);
            cr.accept(new JSRInlinerClassVisitor(cv), ClassReader.SKIP_FRAMES);
            maybeFelixBytes = cw.toByteArray();
        }
        ClassAnalyzer classAnalyzer = new ClassAnalyzer(accv.getThinClass(), advisors, shimTypes,
                mixinTypes, loader, analyzedWorld, codeSource, classBytes);
        if (classAnalyzer.isShortCircuitBeforeAnalyzeMethods()) {
            analyzedWorld.add(classAnalyzer.getAnalyzedClass(), loader);
            return maybeFelixBytes;
        }
        classAnalyzer.analyzeMethods();
        if (!classAnalyzer.isWeavingRequired()) {
            analyzedWorld.add(classAnalyzer.getAnalyzedClass(), loader);
            return maybeFelixBytes;
        }
        // from http://www.oracle.com/technetwork/java/javase/compatibility-417013.html:
        //
        // "Classfiles with version number 51 are exclusively verified using the type-checking
        // verifier, and thus the methods must have StackMapTable attributes when appropriate.
        // For classfiles with version 50, the Hotspot JVM would (and continues to) failover to
        // the type-inferencing verifier if the stackmaps in the file were missing or incorrect.
        // This failover behavior does not occur for classfiles with version 51 (the default
        // version for Java SE 7).
        // Any tool that modifies bytecode in a version 51 classfile must be sure to update the
        // stackmap information to be consistent with the bytecode in order to pass
        // verification."
        //
        ClassWriter cw = new ComputeFramesClassWriter(ClassWriter.COMPUTE_FRAMES, analyzedWorld,
                loader, codeSource, className);
        WeavingClassVisitor cv =
                new WeavingClassVisitor(cw, loader, classAnalyzer.getAnalyzedClass(),
                        classAnalyzer.getMethodsThatOnlyNowFulfillAdvice(),
                        classAnalyzer.getMatchedShimTypes(), classAnalyzer.getMatchedMixinTypes(),
                        classAnalyzer.getMethodAdvisors(), analyzedWorld);
        ClassReader cr = new ClassReader(maybeFelixBytes == null ? classBytes : maybeFelixBytes);
        try {
            cr.accept(new JSRInlinerClassVisitor(cv), ClassReader.SKIP_FRAMES);
        } catch (RuntimeException e) {
            logger.error("unable to weave {}: {}", className, e.getMessage(), e);
            try {
                File tempFile = getTempFile(className, "glowroot-weaving-error-", ".class");
                Files.write(classBytes, tempFile);
                logger.error("wrote bytecode to: {}", tempFile.getAbsolutePath());
            } catch (IOException f) {
                logger.error(f.getMessage(), f);
            }
            return null;
        }
        byte[] transformedBytes = cw.toByteArray();
        if (verifyWeaving) {
            verify(transformedBytes, loader, classBytes, className);
        }
        return transformedBytes;
    }

    private static void verify(byte[] transformedBytes, @Nullable ClassLoader loader,
            byte[] originalBytes, String className) {
        String originalBytesVerifyError = verify(originalBytes, loader);
        if (!originalBytesVerifyError.isEmpty()) {
            // not much to do if original byte code fails to verify
            logger.debug("class verify error for original bytecode\n:" + originalBytesVerifyError);
            return;
        }
        String transformedBytesVerifyError = verify(transformedBytes, loader);
        if (!transformedBytesVerifyError.isEmpty()) {
            logger.error(
                    "class verify error for transformed bytecode\n:" + transformedBytesVerifyError);
            try {
                File originalBytesFile =
                        getTempFile(className, "glowroot-verify-error-", "-original.class");
                Files.write(originalBytes, originalBytesFile);
                logger.error("wrote original bytecode to: {}", originalBytesFile.getAbsolutePath());
                File transformedBytesFile =
                        getTempFile(className, "glowroot-verify-error-", "-transformed.class");
                Files.write(transformedBytes, transformedBytesFile);
                logger.error("wrote transformed bytecode to: {}",
                        transformedBytesFile.getAbsolutePath());
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private static String verify(byte[] bytes, @Nullable ClassLoader loader) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        CheckClassAdapter.verify(new ClassReader(bytes), loader, false, pw);
        pw.close();
        return sw.toString();
    }

    private static File getTempFile(String className, String prefix, String suffix) {
        String tmpDirProperty = StandardSystemProperty.JAVA_IO_TMPDIR.value();
        File tmpDir = tmpDirProperty == null ? new File(".") : new File(tmpDirProperty);
        String simpleName;
        int index = className.lastIndexOf('/');
        if (index == -1) {
            simpleName = className;
        } else {
            simpleName = className.substring(index + 1);
        }
        return new File(tmpDir, prefix + simpleName + suffix);
    }

    private static class JSRInlinerClassVisitor extends ClassVisitor {

        private final ClassVisitor cv;

        private JSRInlinerClassVisitor(ClassVisitor cv) {
            super(ASM5, cv);
            this.cv = cv;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                @Nullable String signature, String/*@Nullable*/[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions);
        }
    }

    @VisibleForTesting
    static class ComputeFramesClassWriter extends ClassWriter {

        private final AnalyzedWorld analyzedWorld;
        private final @Nullable ClassLoader loader;
        private final ParseContext parseContext;

        public ComputeFramesClassWriter(int flags, AnalyzedWorld analyzedWorld,
                @Nullable ClassLoader loader, @Nullable CodeSource codeSource, String className) {
            super(flags);
            this.analyzedWorld = analyzedWorld;
            this.loader = loader;
            this.parseContext = ImmutableParseContext.of(className, codeSource);
        }

        // implements logic similar to org.glowroot.agent.shaded.objectweb.asm.ClassWriter.getCommonSuperClass()
        @Override
        protected String getCommonSuperClass(String type1, String type2) {
            if (type1.equals("java/lang/Object") || type2.equals("java/lang/Object")) {
                return "java/lang/Object";
            }
            try {
                return getCommonSuperClassInternal(type1, type2);
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                return "java/lang/Object";
            }
        }

        private String getCommonSuperClassInternal(String type1, String type2) throws IOException {
            AnalyzedClass analyzedClass1;
            try {
                analyzedClass1 =
                        analyzedWorld.getAnalyzedClass(ClassNames.fromInternalName(type1), loader);
            } catch (ClassNotFoundException e) {
                // log at debug level only since this code will fail anyways if it is actually used
                // at runtime since type doesn't exist
                logger.debug("type {} not found while parsing type {}", type1, parseContext, e);
                return "java/lang/Object";
            }
            AnalyzedClass analyzedClass2;
            try {
                analyzedClass2 =
                        analyzedWorld.getAnalyzedClass(ClassNames.fromInternalName(type2), loader);
            } catch (ClassNotFoundException e) {
                // log at debug level only since this code will fail anyways if it is actually used
                // at runtime since type doesn't exist
                logger.debug("type {} not found while parsing type {}", type2, parseContext, e);
                return "java/lang/Object";
            }
            return getCommonSuperClass(analyzedClass1, analyzedClass2, type1, type2);
        }

        private String getCommonSuperClass(AnalyzedClass analyzedClass1,
                AnalyzedClass analyzedClass2, String type1, String type2) throws IOException {
            if (isAssignableFrom(analyzedClass1.name(), analyzedClass2)) {
                return type1;
            }
            if (isAssignableFrom(analyzedClass2.name(), analyzedClass1)) {
                return type2;
            }
            if (analyzedClass1.isInterface() || analyzedClass2.isInterface()) {
                return "java/lang/Object";
            }
            return getCommonSuperClass(analyzedClass1, analyzedClass2);
        }

        private String getCommonSuperClass(AnalyzedClass analyzedClass1,
                AnalyzedClass analyzedClass2) throws IOException {
            // climb analyzedClass1 super class hierarchy and check if any of them are assignable
            // from analyzedClass2
            String superName = analyzedClass1.superName();
            while (superName != null) {
                if (isAssignableFrom(superName, analyzedClass2)) {
                    return ClassNames.toInternalName(superName);
                }
                try {
                    AnalyzedClass superAnalyzedClass =
                            analyzedWorld.getAnalyzedClass(superName, loader);
                    superName = superAnalyzedClass.superName();
                } catch (ClassNotFoundException e) {
                    // log at debug level only since this code must not be getting used anyways, as
                    // it would fail on execution since the type doesn't exist
                    logger.debug("type {} not found while parsing type {}", superName, parseContext,
                            e);
                    return "java/lang/Object";
                }
            }
            return "java/lang/Object";
        }

        private boolean isAssignableFrom(String possibleSuperClassName, AnalyzedClass analyzedClass)
                throws IOException {
            if (analyzedClass.name().equals(possibleSuperClassName)) {
                return true;
            }
            if (isAssignableFromInterfaces(possibleSuperClassName, analyzedClass)) {
                return true;
            }
            String superName = analyzedClass.superName();
            if (superName == null) {
                return false;
            }
            return isAssignableFromSuperClass(possibleSuperClassName, superName);
        }

        private boolean isAssignableFromInterfaces(String possibleSuperClassName,
                AnalyzedClass analyzedClass) throws IOException {
            for (String interfaceName : analyzedClass.interfaceNames()) {
                try {
                    AnalyzedClass interfaceAnalyzedClass =
                            analyzedWorld.getAnalyzedClass(interfaceName, loader);
                    if (isAssignableFrom(possibleSuperClassName, interfaceAnalyzedClass)) {
                        return true;
                    }
                } catch (ClassNotFoundException e) {
                    // log at debug level only since this code must not be getting used anyways, as
                    // it would fail on execution since the type doesn't exist
                    logger.debug("type {} not found while parsing type {}", interfaceName,
                            parseContext, e);
                }
            }
            return false;
        }

        private boolean isAssignableFromSuperClass(String possibleSuperClassName, String superName)
                throws IOException {
            try {
                AnalyzedClass superAnalyzedClass =
                        analyzedWorld.getAnalyzedClass(superName, loader);
                return isAssignableFrom(possibleSuperClassName, superAnalyzedClass);
            } catch (ClassNotFoundException e) {
                // log at debug level only since this code must not be getting used anyways, as it
                // would fail on execution since the type doesn't exist
                logger.debug("type {} not found while parsing type {}", superName, parseContext, e);
                return false;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy