org.glowroot.agent.weaving.WeavingClassVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glowroot-agent-it-harness Show documentation
Show all versions of glowroot-agent-it-harness Show documentation
Glowroot Agent Integration Test Harness
/*
* Copyright 2012-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.agent.weaving;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableSet;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Iterables;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Lists;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Sets;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.immutables.value.Value;
import org.glowroot.agent.shaded.org.objectweb.asm.ClassReader;
import org.glowroot.agent.shaded.org.objectweb.asm.ClassVisitor;
import org.glowroot.agent.shaded.org.objectweb.asm.ClassWriter;
import org.glowroot.agent.shaded.org.objectweb.asm.FieldVisitor;
import org.glowroot.agent.shaded.org.objectweb.asm.Label;
import org.glowroot.agent.shaded.org.objectweb.asm.MethodVisitor;
import org.glowroot.agent.shaded.org.objectweb.asm.Type;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.AdviceAdapter;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.GeneratorAdapter;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.Method;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.MethodRemapper;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.SimpleRemapper;
import org.glowroot.agent.shaded.org.objectweb.asm.tree.ClassNode;
import org.glowroot.agent.shaded.org.objectweb.asm.tree.FieldNode;
import org.glowroot.agent.shaded.org.objectweb.asm.tree.MethodNode;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;
import org.glowroot.agent.bytecode.api.Bytecode;
import org.glowroot.agent.bytecode.api.Util;
import org.glowroot.agent.plugin.api.ClassInfo;
import org.glowroot.agent.plugin.api.MethodInfo;
import org.glowroot.agent.plugin.api.weaving.Shim;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ALOAD;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ARETURN;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ASM7;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ASTORE;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ATHROW;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.DUP;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.F_NEW;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.GETSTATIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.GOTO;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ICONST_0;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.IFEQ;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.ILOAD;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.INTEGER;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.IRETURN;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.NEW;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.POP;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.RETURN;
import static org.glowroot.agent.shaded.org.objectweb.asm.Opcodes.V1_5;
class WeavingClassVisitor extends ClassVisitor {
private static final Logger logger = LoggerFactory.getLogger(WeavingClassVisitor.class);
private static final Type bytecodeType = Type.getType(Bytecode.class);
private static final Type bytecodeUtilType = Type.getType(Util.class);
private static final Type classInfoType = Type.getType(ClassInfo.class);
private static final Type classInfoImplType = Type.getType(ClassInfoImpl.class);
private static final Type methodInfoType = Type.getType(MethodInfo.class);
private static final Type methodInfoImplType = Type.getType(MethodInfoImpl.class);
private static final AtomicLong metaHolderCounter = new AtomicLong();
private final ClassWriter cw;
private final @Nullable ClassLoader loader;
private final boolean frames;
private final boolean noLongerNeedToWeaveMainMethods;
private final boolean isClassLoader;
private final AnalyzedClass analyzedClass;
private final List methodsThatOnlyNowFulfillAdvice;
private final List shimTypes;
private final List mixinTypes;
private final List mixinClassNodes;
private final Map> methodAdvisors;
private final AnalyzedWorld analyzedWorld;
private final Set shimMethods;
private @MonotonicNonNull Type type;
// these are for handling class and method metas
private final Set classMetaTypes = Sets.newHashSet();
private final Set methodMetaGroups = Sets.newHashSet();
private @MonotonicNonNull String metaHolderInternalName;
private int methodMetaCounter;
private final Set usedAdvisors = Sets.newHashSet();
public WeavingClassVisitor(ClassWriter cw, @Nullable ClassLoader loader, boolean frames,
boolean noLongerNeedToWeaveMainMethods, AnalyzedClass analyzedClass,
boolean isClassLoader, List methodsThatOnlyNowFulfillAdvice,
List shimTypes, List mixinTypes,
Map> methodAdvisors, AnalyzedWorld analyzedWorld) {
super(ASM7, cw);
this.cw = cw;
this.loader = loader;
this.frames = frames;
this.noLongerNeedToWeaveMainMethods = noLongerNeedToWeaveMainMethods;
this.isClassLoader = isClassLoader;
this.analyzedClass = analyzedClass;
this.methodsThatOnlyNowFulfillAdvice = methodsThatOnlyNowFulfillAdvice;
this.shimTypes = shimTypes;
this.mixinTypes = mixinTypes;
this.methodAdvisors = methodAdvisors;
this.analyzedWorld = analyzedWorld;
shimMethods = Sets.newHashSet();
for (ShimType shimType : shimTypes) {
for (java.lang.reflect.Method shimMethod : shimType.shimMethods()) {
Method method = Method.getMethod(shimMethod);
shimMethods.add(method.getName() + method.getDescriptor());
}
}
// cannot store ClassNode in MixinType and re-use across MethodClassVisitors because
// MethodNode.accept() cannot be called multiple times (at least not across multiple
// threads) without throwing an occassional NPE
mixinClassNodes = Lists.newArrayList();
if (!analyzedClass.isInterface()) {
for (MixinType mixinType : mixinTypes) {
ClassReader cr = new ClassReader(mixinType.implementationBytes());
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
mixinClassNodes.add(cn);
}
}
}
@Override
public void visit(int version, int access, String internalName, @Nullable String signature,
@Nullable String superInternalName,
String /*@Nullable*/ [] interfaceInternalNamesNullable) {
type = Type.getObjectType(internalName);
String[] interfacesPlus = interfaceInternalNamesNullable;
if (!analyzedClass.isInterface()) {
// do not add Shim/Mixin interfaces to an interface, otherwise if a lambda implements
// the interface, it will pick up the inherited Shim/Mixin interfaces, but the
// Shim/Mixin methods will not be implemented on Java 9+ since lambda classes are not
// passed to ClassFileTransformer in Java 9+
interfacesPlus = getInterfacesIncludingShimsAndMixins(interfaceInternalNamesNullable,
shimTypes, mixinTypes);
}
cw.visit(version, access, internalName, signature, superInternalName, interfacesPlus);
}
@Override
public @Nullable MethodVisitor visitMethod(int access, String name, String descriptor,
@Nullable String signature, String /*@Nullable*/ [] exceptions) {
checkNotNull(type);
if (isAbstractOrNativeOrSynthetic(access)) {
// don't try to weave abstract, native and synthetic methods
// no need to weave bridge methods (which are also marked synthetic) since they forward
// to non-bridged method which receives same advice (see ClassAnalyzer
// bridgeTargetAdvisors)
return cw.visitMethod(access, name, descriptor, signature, exceptions);
}
if (isMixinProxy(name, descriptor)) {
return null;
}
if (shimMethods.contains(name + descriptor)) {
// ignore proxy implementations of shim methods (they will be implemented in visitEnd())
return null;
}
List matchingAdvisors = methodAdvisors.get(name + descriptor);
if (matchingAdvisors == null) {
matchingAdvisors = ImmutableList.of();
} else {
matchingAdvisors = removeSuperseded(matchingAdvisors);
}
if (isInitWithMixins(name)) {
return visitInitWithMixins(access, name, descriptor, signature, exceptions,
matchingAdvisors);
}
MethodVisitor mv = cw.visitMethod(access, name, descriptor, signature, exceptions);
if (!noLongerNeedToWeaveMainMethods) {
if (name.equals("main") && Modifier.isPublic(access) && Modifier.isStatic(access)
&& descriptor.equals("([Ljava/lang/String;)V")) {
mv.visitLdcInsn(type.getClassName());
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(),
"enteringMainMethod", "(Ljava/lang/String;[Ljava/lang/String;)V", false);
} else if (type.getInternalName()
.equals("org/apache/commons/daemon/support/DaemonLoader")
&& Modifier.isPublic(access) && Modifier.isStatic(access) && name.equals("load")
&& descriptor.equals("(Ljava/lang/String;[Ljava/lang/String;)Z")) {
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(),
"enteringApacheCommonsDaemonLoadMethod",
"(Ljava/lang/String;[Ljava/lang/String;)V", false);
}
}
if (isClassLoader && name.equals("loadClass")
&& (Modifier.isPublic(access) || Modifier.isProtected(access))
&& !Modifier.isStatic(access)) {
if (descriptor.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
addLoadClassConditional(mv,
new Object[] {type.getInternalName(), "java/lang/String"});
} else if (descriptor.equals("(Ljava/lang/String;Z)Ljava/lang/Class;")) {
addLoadClassConditional(mv,
new Object[] {type.getInternalName(), "java/lang/String", INTEGER});
}
}
if (matchingAdvisors.isEmpty()) {
return mv;
}
return visitMethodWithAdvice(mv, access, name, descriptor, matchingAdvisors);
}
Set getUsedAdvisors() {
return usedAdvisors;
}
private boolean isMixinProxy(String name, String descriptor) {
for (ClassNode cn : mixinClassNodes) {
List methodNodes = cn.methods;
for (MethodNode mn : methodNodes) {
if (!mn.name.equals("") && mn.name.equals(name)
&& mn.desc.equals(descriptor)) {
return true;
}
}
}
return false;
}
@Override
public void visitEnd() {
checkNotNull(type);
analyzedWorld.add(analyzedClass, loader);
if (!analyzedClass.isInterface()) {
for (ShimType shimType : shimTypes) {
addShim(shimType);
}
for (ClassNode mixinClassNode : mixinClassNodes) {
addMixin(mixinClassNode);
}
}
for (AnalyzedMethod methodThatOnlyNowFulfillAdvice : methodsThatOnlyNowFulfillAdvice) {
overrideAndWeaveInheritedMethod(methodThatOnlyNowFulfillAdvice);
}
// handle metas at end, since handleInheritedMethodsThatNowFulfillAdvice()
// above could add new metas
if (metaHolderInternalName != null) {
handleMetaHolders();
}
cw.visitEnd();
}
@RequiresNonNull({"type", "metaHolderInternalName"})
private void handleMetaHolders() {
if (loader == null) {
initializeBoostrapMetaHolders();
} else {
try {
generateMetaHolder();
} catch (Exception e) {
// this will terminate weaving and get logged by WeavingClassFileTransformer
throw new RuntimeException(e);
}
}
}
@RequiresNonNull({"type", "metaHolderInternalName"})
private void initializeBoostrapMetaHolders() {
for (Type classMetaType : classMetaTypes) {
String classMetaInternalName = classMetaType.getInternalName();
String classMetaFieldName =
"glowroot$class$meta$" + classMetaInternalName.replace('/', '$');
BootstrapMetaHolders.createClassMetaHolder(metaHolderInternalName, classMetaFieldName,
classMetaType, type);
}
for (MethodMetaGroup methodMetaGroup : methodMetaGroups) {
for (Type methodMetaType : methodMetaGroup.methodMetaTypes()) {
String methodMetaInternalName = methodMetaType.getInternalName();
String methodMetaFieldName = "glowroot$method$meta$" + methodMetaGroup.uniqueNum()
+ '$' + methodMetaInternalName.replace('/', '$');
BootstrapMetaHolders.createMethodMetaHolder(metaHolderInternalName,
methodMetaFieldName, methodMetaType, type, methodMetaGroup.methodName(),
methodMetaGroup.methodReturnType(), methodMetaGroup.methodParameterTypes());
}
}
}
@RequiresNonNull({"type", "metaHolderInternalName", "loader"})
private void generateMetaHolder() throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, metaHolderInternalName, null, "java/lang/Object",
null);
Type metaHolderType = Type.getObjectType(metaHolderInternalName);
MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
mv.visitLabel(l0);
for (Type classMetaType : classMetaTypes) {
String classMetaInternalName = classMetaType.getInternalName();
String classMetaFieldName =
"glowroot$class$meta$" + classMetaInternalName.replace('/', '$');
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, classMetaFieldName,
"L" + classMetaInternalName + ";", null, null);
fv.visitEnd();
mv.visitTypeInsn(NEW, classMetaInternalName);
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, classInfoImplType.getInternalName());
mv.visitInsn(DUP);
mv.visitLdcInsn(type.getClassName());
mv.visitLdcInsn(metaHolderType);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
"()Ljava/lang/ClassLoader;", false);
mv.visitMethodInsn(INVOKESPECIAL, classInfoImplType.getInternalName(), "",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V", false);
mv.visitMethodInsn(INVOKESPECIAL, classMetaInternalName, "",
"(L" + classInfoType.getInternalName() + ";)V", false);
mv.visitFieldInsn(PUTSTATIC, metaHolderInternalName, classMetaFieldName,
"L" + classMetaInternalName + ";");
}
for (MethodMetaGroup methodMetaGroup : methodMetaGroups) {
for (Type methodMetaType : methodMetaGroup.methodMetaTypes()) {
String methodMetaInternalName = methodMetaType.getInternalName();
String methodMetaFieldName = "glowroot$method$meta$" + methodMetaGroup.uniqueNum()
+ '$' + methodMetaInternalName.replace('/', '$');
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
methodMetaFieldName, "L" + methodMetaInternalName + ";", null, null);
fv.visitEnd();
mv.visitTypeInsn(NEW, methodMetaInternalName);
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, methodInfoImplType.getInternalName());
mv.visitInsn(DUP);
mv.visitLdcInsn(methodMetaGroup.methodName());
loadType(mv, methodMetaGroup.methodReturnType(), metaHolderType);
mv.visitTypeInsn(NEW, "java/util/ArrayList");
mv.visitInsn(DUP);
List methodParameterTypes = methodMetaGroup.methodParameterTypes();
mv.visitLdcInsn(methodParameterTypes.size());
mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "(I)V", false);
for (int i = 0; i < methodParameterTypes.size(); i++) {
mv.visitInsn(DUP);
loadType(mv, methodParameterTypes.get(i), metaHolderType);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add",
"(Ljava/lang/Object;)Z", true);
mv.visitInsn(POP);
}
mv.visitLdcInsn(type.getClassName());
mv.visitLdcInsn(metaHolderType);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
"()Ljava/lang/ClassLoader;", false);
mv.visitMethodInsn(INVOKESPECIAL, methodInfoImplType.getInternalName(), "",
"(Ljava/lang/String;Ljava/lang/Class;Ljava/util/List;Ljava/lang/String;"
+ "Ljava/lang/ClassLoader;)V",
false);
mv.visitMethodInsn(INVOKESPECIAL, methodMetaInternalName, "",
"(L" + methodInfoType.getInternalName() + ";)V", false);
mv.visitFieldInsn(PUTSTATIC, metaHolderInternalName, methodMetaFieldName,
"L" + methodMetaInternalName + ";");
}
}
// catch/log/re-throw
mv.visitLabel(l1);
Label l3 = new Label();
mv.visitJumpInsn(GOTO, l3);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(), "logThrowable",
"(Ljava/lang/Throwable;)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
ClassLoaders.defineClass(ClassNames.fromInternalName(metaHolderInternalName), bytes,
loader);
}
private static void loadType(MethodVisitor mv, Type type, Type ownerType) {
switch (type.getSort()) {
case Type.VOID:
mv.visitFieldInsn(GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
break;
case Type.BOOLEAN:
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
break;
case Type.CHAR:
mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
break;
case Type.BYTE:
mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
break;
case Type.SHORT:
mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
break;
case Type.INT:
mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
break;
case Type.FLOAT:
mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
break;
case Type.LONG:
mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
break;
case Type.DOUBLE:
mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
break;
case Type.ARRAY:
loadArrayType(mv, type, ownerType);
break;
default:
loadObjectType(mv, type, ownerType);
}
}
private static void loadArrayType(MethodVisitor mv, Type type, Type ownerType) {
loadType(mv, type.getElementType(), ownerType);
mv.visitLdcInsn(type.getDimensions());
mv.visitMethodInsn(INVOKESTATIC, bytecodeUtilType.getInternalName(),
"getArrayClass", "(Ljava/lang/Class;I)Ljava/lang/Class;", false);
}
private static void loadObjectType(MethodVisitor mv, Type type, Type ownerType) {
// may not have access to type in meta holder (e.g. if type is private), so need to use
// Class.forName() instead of class constant
mv.visitLdcInsn(type.getClassName());
mv.visitInsn(ICONST_0);
mv.visitLdcInsn(ownerType);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
"()Ljava/lang/ClassLoader;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", false);
}
private static String /*@Nullable*/ [] getInterfacesIncludingShimsAndMixins(
String /*@Nullable*/ [] interfaces, List shimTypes,
List mixinTypes) {
if (mixinTypes.isEmpty() && shimTypes.isEmpty()) {
return interfaces;
}
Set interfacesIncludingShimsAndMixins = Sets.newHashSet();
if (interfaces != null) {
interfacesIncludingShimsAndMixins.addAll(Arrays.asList(interfaces));
}
for (ShimType matchedShimType : shimTypes) {
interfacesIncludingShimsAndMixins.add(matchedShimType.iface().getInternalName());
}
for (MixinType matchedMixinType : mixinTypes) {
for (Type mixinInterface : matchedMixinType.interfaces()) {
interfacesIncludingShimsAndMixins.add(mixinInterface.getInternalName());
}
}
return Iterables.toArray(interfacesIncludingShimsAndMixins, String.class);
}
private boolean isInitWithMixins(String name) {
return name.equals("") && !mixinTypes.isEmpty();
}
@RequiresNonNull("type")
private MethodVisitor visitInitWithMixins(int access, String name, String descriptor,
@Nullable String signature, String /*@Nullable*/ [] exceptions,
List matchingAdvisors) {
MethodVisitor mv = cw.visitMethod(access, name, descriptor, signature, exceptions);
mv = new InitMixins(mv, access, name, descriptor, mixinTypes, type);
for (Advice advice : matchingAdvisors) {
if (!advice.pointcut().timerName().isEmpty()) {
logger.warn("cannot add timer to or methods at this time");
break;
}
}
return newWeavingMethodVisitor(access, name, descriptor, matchingAdvisors, mv);
}
@RequiresNonNull("type")
private MethodVisitor visitMethodWithAdvice(MethodVisitor mv, int access, String name,
String descriptor, List matchingAdvisors) {
// FIXME remove superseded advisors
return newWeavingMethodVisitor(access, name, descriptor, matchingAdvisors, mv);
}
@RequiresNonNull("type")
private WeavingMethodVisitor newWeavingMethodVisitor(int access, String name, String descriptor,
List matchingAdvisors, MethodVisitor mv) {
Integer methodMetaUniqueNum = collectMetasAtMethod(matchingAdvisors, name, descriptor);
usedAdvisors.addAll(matchingAdvisors);
return new WeavingMethodVisitor(mv, frames, access, name, descriptor, type,
matchingAdvisors, metaHolderInternalName, methodMetaUniqueNum, loader == null);
}
private @Nullable Integer collectMetasAtMethod(Iterable matchingAdvisors,
String methodName, String methodDesc) {
Set methodMetaTypes = Sets.newHashSet();
for (Advice matchingAdvice : matchingAdvisors) {
classMetaTypes.addAll(matchingAdvice.classMetaTypes());
methodMetaTypes.addAll(matchingAdvice.methodMetaTypes());
}
Integer methodMetaUniqueNum = null;
if (!methodMetaTypes.isEmpty()) {
methodMetaUniqueNum = ++methodMetaCounter;
methodMetaGroups.add(ImmutableMethodMetaGroup.builder()
.methodName(methodName)
.methodReturnType(Type.getReturnType(methodDesc))
.addMethodParameterTypes(Type.getArgumentTypes(methodDesc))
.uniqueNum(methodMetaUniqueNum)
.addAllMethodMetaTypes(methodMetaTypes)
.build());
}
if ((!classMetaTypes.isEmpty() || !methodMetaTypes.isEmpty())
&& metaHolderInternalName == null) {
metaHolderInternalName =
"org/glowroot/agent/weaving/MetaHolder" + metaHolderCounter.incrementAndGet();
}
return methodMetaUniqueNum;
}
@RequiresNonNull("type")
private void addShim(ShimType shimType) {
for (java.lang.reflect.Method reflectMethod : shimType.shimMethods()) {
Method method = Method.getMethod(reflectMethod);
Shim shim = reflectMethod.getAnnotation(Shim.class);
checkNotNull(shim);
if (shim.value().length != 1) {
throw new IllegalStateException(
"@Shim annotation must have exactly one value when used on methods");
}
Method targetMethod = Method.getMethod(shim.value()[0]);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(), method.getDescriptor(),
null, null);
mv.visitCode();
int i = 0;
mv.visitVarInsn(ALOAD, i++);
for (Type argumentType : method.getArgumentTypes()) {
mv.visitVarInsn(argumentType.getOpcode(ILOAD), i++);
}
mv.visitMethodInsn(INVOKEVIRTUAL, type.getInternalName(), targetMethod.getName(),
targetMethod.getDescriptor(), false);
mv.visitInsn(method.getReturnType().getOpcode(IRETURN));
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
@RequiresNonNull("type")
private void addMixin(ClassNode mixinClassNode) {
List fieldNodes = mixinClassNode.fields;
for (FieldNode fieldNode : fieldNodes) {
if (!Modifier.isTransient(fieldNode.access)) {
// this is needed to avoid serialization issues (even if the new field is
// serializable, this can still cause issues in a cluster if glowroot is not
// deployed on all nodes)
throw new IllegalStateException(
"@Mixin fields must be marked transient: " + mixinClassNode.name);
}
fieldNode.accept(this);
}
List methodNodes = mixinClassNode.methods;
for (MethodNode mn : methodNodes) {
if (mn.name.equals("")) {
continue;
}
String[] exceptions = Iterables.toArray(mn.exceptions, String.class);
MethodVisitor mv =
cw.visitMethod(mn.access, mn.name, mn.desc, mn.signature, exceptions);
mn.accept(new MethodRemapper(mv,
new SimpleRemapper(mixinClassNode.name, type.getInternalName())));
}
}
@RequiresNonNull("type")
private void overrideAndWeaveInheritedMethod(AnalyzedMethod inheritedMethod) {
String superName = analyzedClass.superName();
// superName is null only for java.lang.Object which doesn't inherit anything
// so safe to assume superName not null here
checkNotNull(superName);
String[] exceptions = new String[inheritedMethod.exceptions().size()];
for (int i = 0; i < inheritedMethod.exceptions().size(); i++) {
exceptions[i] = ClassNames.toInternalName(inheritedMethod.exceptions().get(i));
}
List advisors = removeSuperseded(inheritedMethod.advisors());
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, inheritedMethod.name(),
inheritedMethod.getDesc(), inheritedMethod.signature(), exceptions);
mv = visitMethodWithAdvice(mv, ACC_PUBLIC, inheritedMethod.name(),
inheritedMethod.getDesc(), advisors);
checkNotNull(mv);
GeneratorAdapter mg = new GeneratorAdapter(mv, ACC_PUBLIC, inheritedMethod.name(),
inheritedMethod.getDesc());
mg.visitCode();
mg.loadThis();
mg.loadArgs();
Type superType = Type.getObjectType(ClassNames.toInternalName(superName));
// method is called invokeConstructor, but should really be called invokeSpecial
Method method = new Method(inheritedMethod.name(), inheritedMethod.getDesc());
mg.invokeConstructor(superType, method);
mg.returnValue();
mg.endMethod();
}
private static List removeSuperseded(List advisors) {
if (advisors.size() < 2) {
// common case optimization
return advisors;
}
Set suppressionKeys = Sets.newHashSet();
for (Advice advice : advisors) {
String suppressionKey = advice.pointcut().suppressionKey();
if (!suppressionKey.isEmpty()) {
suppressionKeys.add(suppressionKey);
}
}
if (suppressionKeys.isEmpty()) {
// common case optimization
return advisors;
}
List filteredAdvisors = Lists.newArrayList();
for (Advice advice : advisors) {
String suppressibleUsingKey = advice.pointcut().suppressibleUsingKey();
if (suppressibleUsingKey.isEmpty() || !suppressionKeys.contains(suppressibleUsingKey)) {
filteredAdvisors.add(advice);
}
}
return filteredAdvisors;
}
private static boolean isAbstractOrNativeOrSynthetic(int access) {
return Modifier.isAbstract(access) || Modifier.isNative(access)
|| (access & ACC_SYNTHETIC) != 0;
}
private static void addLoadClassConditional(MethodVisitor mv, Object[] locals) {
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
mv.visitTryCatchBlock(label0, label1, label2, "java/lang/ClassNotFoundException");
mv.visitVarInsn(ALOAD, 1);
mv.visitLdcInsn("org.glowroot.agent");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "startsWith", "(Ljava/lang/String;)Z",
false);
Label label3 = new Label();
mv.visitJumpInsn(IFEQ, label3);
mv.visitLabel(label0);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(ICONST_0);
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", false);
mv.visitLabel(label1);
mv.visitInsn(ARETURN);
mv.visitLabel(label2);
mv.visitFrame(F_NEW, locals.length, locals, 1,
new Object[] {"java/lang/ClassNotFoundException"});
mv.visitInsn(POP);
mv.visitLabel(label3);
mv.visitFrame(F_NEW, locals.length, locals, 0, new Object[0]);
}
private static class InitMixins extends AdviceAdapter {
private final ImmutableList matchedMixinTypes;
private final Type type;
private boolean cascadingConstructor;
InitMixins(MethodVisitor mv, int access, String name, String descriptor,
List matchedMixinTypes, Type type) {
super(ASM7, mv, access, name, descriptor);
this.matchedMixinTypes = ImmutableList.copyOf(matchedMixinTypes);
this.type = type;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor,
boolean itf) {
if (name.equals("") && owner.equals(type.getInternalName())) {
cascadingConstructor = true;
}
super.visitMethodInsn(opcode, owner, name, descriptor, itf);
}
@Override
protected void onMethodExit(int opcode) {
if (cascadingConstructor) {
// need to call MixinInit exactly once, so don't call MixinInit at end of cascading
// constructors
return;
}
for (MixinType mixinType : matchedMixinTypes) {
String initMethodName = mixinType.initMethodName();
if (initMethodName != null) {
loadThis();
invokeVirtual(type, new Method(initMethodName, "()V"));
}
}
}
}
@Value.Immutable
interface MethodMetaGroup {
String methodName();
Type methodReturnType();
ImmutableList methodParameterTypes();
int uniqueNum();
ImmutableSet methodMetaTypes();
}
}