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

org.glowroot.weaving.AnalyzingClassVisitor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2015 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.weaving;

import java.lang.reflect.Modifier;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Sets;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.shaded.objectweb.asm.ClassVisitor;
import org.glowroot.shaded.objectweb.asm.MethodVisitor;
import org.glowroot.shaded.objectweb.asm.Type;

import org.glowroot.common.ClassNames;
import org.glowroot.weaving.WeavingClassVisitor.ShortCircuitException;

import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.shaded.objectweb.asm.Opcodes.ASM5;

class AnalyzingClassVisitor extends ClassVisitor {

    private final ImmutableList shimTypes;
    private final ImmutableList mixinTypes;
    private final ImmutableList advisors;
    private final @Nullable ClassLoader loader;
    private final AnalyzedWorld analyzedWorld;
    private final @Nullable CodeSource codeSource;

    private ImmutableList adviceMatchers = ImmutableList.of();
    private ImmutableList matchedShimTypes = ImmutableList.of();
    private ImmutableList matchedMixinTypes = ImmutableList.of();

    private List superAnalyzedClasses = ImmutableList.of();

    private AnalyzedClass./*@MonotonicNonNull*/Builder analyzedClassBuilder;

    private @MonotonicNonNull AnalyzedClass analyzedClass;

    private @MonotonicNonNull String className;
    private @MonotonicNonNull Set superClassNames;

    public AnalyzingClassVisitor(List advisors, List shimTypes,
            List mixinTypes, @Nullable ClassLoader loader, AnalyzedWorld analyzedWorld,
            @Nullable CodeSource codeSource) {
        super(ASM5);
        this.shimTypes = ImmutableList.copyOf(shimTypes);
        this.mixinTypes = ImmutableList.copyOf(mixinTypes);
        this.advisors = ImmutableList.copyOf(advisors);
        this.loader = loader;
        this.analyzedWorld = analyzedWorld;
        this.codeSource = codeSource;
    }

    @Override
    public void visit(int version, int access, String internalName, @Nullable String signature,
            @Nullable String superInternalName,
            String /*@Nullable*/[] interfaceInternalNamesNullable) {

        AnalyzedClass nonInterestingAnalyzedClass =
                visitAndSometimesReturnNonInterestingAnalyzedClass(access, internalName,
                        superInternalName, interfaceInternalNamesNullable);
        if (nonInterestingAnalyzedClass != null) {
            analyzedClass = nonInterestingAnalyzedClass;
            throw ShortCircuitException.INSTANCE;
        }
    }

    public @Nullable AnalyzedClass visitAndSometimesReturnNonInterestingAnalyzedClass(int access,
            String internalName, @Nullable String superInternalName,
            String /*@Nullable*/[] interfaceInternalNamesNullable) {

        ImmutableList interfaceNames =
                ClassNames.fromInternalNames(interfaceInternalNamesNullable);
        className = ClassNames.fromInternalName(internalName);
        String superClassName = ClassNames.fromInternalName(superInternalName);
        analyzedClassBuilder = AnalyzedClass.builder()
                .modifiers(access)
                .name(className)
                .superName(superClassName)
                .addAllInterfaceNames(interfaceNames);
        adviceMatchers = AdviceMatcher.getAdviceMatchers(className, advisors);
        if (Modifier.isInterface(access)) {
            superAnalyzedClasses = ImmutableList.of();
            matchedShimTypes = getMatchedShimTypes(className, ImmutableList.of(),
                    ImmutableList.of());
            analyzedClassBuilder.addAllShimTypes(matchedShimTypes);
            matchedMixinTypes = getMatchedMixinTypes(className, ImmutableList.of(),
                    ImmutableList.of());
            analyzedClassBuilder.addAllMixinTypes(matchedMixinTypes);
            if (adviceMatchers.isEmpty()) {
                return analyzedClassBuilder.build();
            } else {
                return null;
            }
        }
        ParseContext parseContext = ParseContext.of(className, codeSource);
        List superAnalyzedHierarchy =
                analyzedWorld.getAnalyzedHierarchy(superClassName, loader, parseContext);
        List interfaceAnalyzedHierarchy =
                getAnalyzedHierarchy(interfaceNames, parseContext);
        // it's ok if there are duplicates in the superAnalyzedClasses list (e.g. an interface that
        // appears twice in a type hierarchy), it's rare, dups don't cause an issue for callers, and
        // so it doesn't seem worth the (minor) performance hit to de-dup every time
        superAnalyzedClasses = Lists.newArrayList();
        superAnalyzedClasses.addAll(superAnalyzedHierarchy);
        superAnalyzedClasses.addAll(interfaceAnalyzedHierarchy);
        matchedShimTypes =
                getMatchedShimTypes(className, superAnalyzedHierarchy, interfaceAnalyzedHierarchy);
        analyzedClassBuilder.addAllShimTypes(matchedShimTypes);
        matchedMixinTypes =
                getMatchedMixinTypes(className, superAnalyzedHierarchy, interfaceAnalyzedHierarchy);
        analyzedClassBuilder.addAllMixinTypes(matchedMixinTypes);
        if (isNonInteresting()) {
            return analyzedClassBuilder.build();
        } else {
            return null;
        }
    }

    @Override
    public @Nullable MethodVisitor visitMethod(int access, String name, String desc,
            @Nullable String signature, String /*@Nullable*/[] exceptions) {
        visitMethodAndReturnAdvisors(access, name, desc, signature, exceptions);
        return null;
    }

    @Override
    public void visitEnd() {
        checkNotNull(analyzedClassBuilder, "Call to visit() is required");
        analyzedClass = analyzedClassBuilder.build();
    }

    List visitMethodAndReturnAdvisors(int access, String name, String desc,
            @Nullable String signature, String /*@Nullable*/[] exceptions) {
        List parameterTypes = Arrays.asList(Type.getArgumentTypes(desc));
        Type returnType = Type.getReturnType(desc);
        List matchingAdvisors =
                getMatchingAdvisors(name, parameterTypes, returnType, access);
        if (!matchingAdvisors.isEmpty()) {
            checkNotNull(analyzedClassBuilder, "Call to visit() is required");
            AnalyzedMethod.Builder builder = AnalyzedMethod.builder();
            builder.name(name);
            for (Type parameterType : parameterTypes) {
                builder.addParameterTypes(parameterType.getClassName());
            }
            builder.returnType(returnType.getClassName())
                    .modifiers(access)
                    .signature(signature);
            if (exceptions != null) {
                for (String exception : exceptions) {
                    builder.addExceptions(ClassNames.fromInternalName(exception));
                }
            }
            builder.addAllAdvisors(matchingAdvisors);
            analyzedClassBuilder.addAnalyzedMethods(builder.build());
        }
        return matchingAdvisors;
    }

    ImmutableList getMatchedShimTypes() {
        return matchedShimTypes;
    }

    ImmutableList getMatchedMixinTypes() {
        return matchedMixinTypes;
    }

    List getSuperAnalyzedClasses() {
        return superAnalyzedClasses;
    }

    @Nullable
    AnalyzedClass getAnalyzedClass() {
        return analyzedClass;
    }

    private List getAnalyzedHierarchy(ImmutableList classNames,
            ParseContext parseContext) {
        List analyzedHierarchy = Lists.newArrayList();
        for (String className : classNames) {
            analyzedHierarchy
                    .addAll(analyzedWorld.getAnalyzedHierarchy(className, loader, parseContext));
        }
        return analyzedHierarchy;
    }

    private ImmutableList getMatchedShimTypes(String className,
            Iterable superAnalyzedClasses,
            List newInterfaceAnalyzedClasses) {
        Set matchedShimTypes = Sets.newHashSet();
        for (ShimType shimType : shimTypes) {
            if (shimType.target().equals(className)) {
                matchedShimTypes.add(shimType);
            }
        }
        for (AnalyzedClass newInterfaceAnalyzedClass : newInterfaceAnalyzedClasses) {
            matchedShimTypes.addAll(newInterfaceAnalyzedClass.shimTypes());
        }
        // remove shims that were already implemented in a super class
        for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
            if (!superAnalyzedClass.isInterface()) {
                matchedShimTypes.removeAll(superAnalyzedClass.shimTypes());
            }
        }
        return ImmutableList.copyOf(matchedShimTypes);
    }

    private ImmutableList getMatchedMixinTypes(String className,
            Iterable superAnalyzedClasses,
            List newInterfaceAnalyzedClasses) {
        Set matchedMixinTypes = Sets.newHashSet();
        for (MixinType mixinType : mixinTypes) {
            // currently only exact matching is supported
            if (mixinType.targets().contains(className)) {
                matchedMixinTypes.add(mixinType);
            }
        }
        for (AnalyzedClass newInterfaceAnalyzedClass : newInterfaceAnalyzedClasses) {
            matchedMixinTypes.addAll(newInterfaceAnalyzedClass.mixinTypes());
        }
        // remove mixins that were already implemented in a super class
        for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
            if (!superAnalyzedClass.isInterface()) {
                matchedMixinTypes.removeAll(superAnalyzedClass.mixinTypes());
            }
        }
        return ImmutableList.copyOf(matchedMixinTypes);
    }

    private boolean isNonInteresting() {
        return !hasSuperAdvice() && adviceMatchers.isEmpty() && matchedShimTypes.isEmpty()
                && matchedMixinTypes.isEmpty();
    }

    private boolean hasSuperAdvice() {
        for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
            if (!superAnalyzedClass.analyzedMethods().isEmpty()) {
                return true;
            }
        }
        return false;
    }

    private List getMatchingAdvisors(String methodName, List parameterTypes,
            Type returnType, int modifiers) {
        Set matchingAdvisors = Sets.newHashSet();
        for (AdviceMatcher adviceMatcher : adviceMatchers) {
            if (adviceMatcher.isMethodLevelMatch(methodName, parameterTypes, returnType,
                    modifiers)) {
                matchingAdvisors.add(adviceMatcher.advice());
            }
        }
        // look at super types
        checkNotNull(superAnalyzedClasses, "Call to visit() is required");
        for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
            for (AnalyzedMethod analyzedMethod : superAnalyzedClass.analyzedMethods()) {
                if (analyzedMethod.isOverriddenBy(methodName, parameterTypes)) {
                    addToMatchingAdvisors(matchingAdvisors, analyzedMethod.advisors());
                }
            }
        }
        // sort since the order affects advice and timer nesting
        return sortAdvisors(matchingAdvisors);
    }

    void addToMatchingAdvisors(Set matchingAdvisors, List advisors) {
        for (Advice advice : advisors) {
            if (advice.pointcut().declaringClassName().equals("")) {
                matchingAdvisors.add(advice);
            } else {
                if (isTargetClassNameMatch(advice)) {
                    matchingAdvisors.add(advice);
                }
            }
        }
    }

    private boolean isTargetClassNameMatch(Advice advice) {
        if (superClassNames == null) {
            superClassNames = buildSuperClassNames();
        }
        Pattern targetClassNamePattern = advice.pointcutTargetClassNamePattern();
        if (targetClassNamePattern == null) {
            return superClassNames.contains(advice.pointcutTargetClassName());
        } else {
            for (String superClassName : superClassNames) {
                if (targetClassNamePattern.matcher(superClassName).matches()) {
                    return true;
                }
            }
            return false;
        }
    }

    private Set buildSuperClassNames() {
        checkNotNull(className, "Call to visit() is required");
        Set superClassNames = Sets.newHashSet();
        superClassNames.add(className);
        for (AnalyzedClass analyzedClass : superAnalyzedClasses) {
            superClassNames.add(analyzedClass.name());
        }
        return superClassNames;
    }

    private List sortAdvisors(Set matchingAdvisors) {
        switch (matchingAdvisors.size()) {
            case 0:
                return ImmutableList.of();
            case 1:
                return ImmutableList.copyOf(matchingAdvisors);
            default:
                return Advice.ordering.immutableSortedCopy(matchingAdvisors);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy