Split FieldBackedImplementationInstaller into several smaller classes (#4297)

This commit is contained in:
Mateusz Rzeszutek 2021-10-05 20:25:39 +02:00 committed by GitHub
parent 7791be24e0
commit 09ad3d2f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1081 additions and 868 deletions

View File

@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getFieldAccessorInterfaceName;
import java.util.Collection;
import java.util.Map;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
final class FieldAccessorInterfaces {
// field-accessor-interface-name -> fields-accessor-interface-dynamic-type
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
FieldAccessorInterfaces(Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces) {
this.fieldAccessorInterfaces = fieldAccessorInterfaces;
}
TypeDescription find(String typeName, String fieldTypeName) {
String accessorInterfaceName = getFieldAccessorInterfaceName(typeName, fieldTypeName);
DynamicType.Unloaded<?> type = fieldAccessorInterfaces.get(accessorInterfaceName);
if (type == null) {
throw new IllegalStateException(
"Couldn't find field accessor interface named " + accessorInterfaceName);
}
return type.getTypeDescription();
}
Collection<DynamicType.Unloaded<?>> getAllInterfaces() {
return fieldAccessorInterfaces.values();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getFieldAccessorInterfaceName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealGetterName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealSetterName;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.SyntheticState;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
final class FieldAccessorInterfacesGenerator {
private final ByteBuddy byteBuddy;
FieldAccessorInterfacesGenerator(ByteBuddy byteBuddy) {
this.byteBuddy = byteBuddy;
}
FieldAccessorInterfaces generateFieldAccessorInterfaces(
VirtualFieldMappings virtualFieldMappings) {
Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces =
new HashMap<>(virtualFieldMappings.size());
for (Map.Entry<String, String> entry : virtualFieldMappings.entrySet()) {
DynamicType.Unloaded<?> type = makeFieldAccessorInterface(entry.getKey(), entry.getValue());
fieldAccessorInterfaces.put(type.getTypeDescription().getName(), type);
}
return new FieldAccessorInterfaces(fieldAccessorInterfaces);
}
/**
* Generate an interface that provides field accessor methods for given key class name and context
* class name.
*
* @param typeName key class name
* @param fieldTypeName context class name
* @return unloaded dynamic type containing generated interface
*/
private DynamicType.Unloaded<?> makeFieldAccessorInterface(
String typeName, String fieldTypeName) {
// We are using Object class name instead of fieldTypeName here because this gets injected
// onto Bootstrap classloader where context class may be unavailable
TypeDescription fieldTypeDesc = new TypeDescription.ForLoadedType(Object.class);
return byteBuddy
.makeInterface()
.merge(SyntheticState.SYNTHETIC)
.name(getFieldAccessorInterfaceName(typeName, fieldTypeName))
.defineMethod(getRealGetterName(typeName), fieldTypeDesc, Visibility.PUBLIC)
.withoutCode()
.defineMethod(getRealSetterName(typeName), TypeDescription.VOID, Visibility.PUBLIC)
.withParameter(fieldTypeDesc, "value")
.withoutCode()
.make();
}
}

View File

@ -10,50 +10,24 @@ import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import io.opentelemetry.instrumentation.api.caching.Cache;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
import io.opentelemetry.javaagent.tooling.HelperInjector;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.instrumentation.InstrumentationModuleInstaller;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.modifier.SyntheticState;
import net.bytebuddy.description.modifier.TypeManifestation;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
/**
@ -66,10 +40,11 @@ import net.bytebuddy.utility.JavaModule;
* <li>Injecting a Dynamic Interface that provides getter and setter for context field
* <li>Applying Dynamic Interface to a type needing context, implementing interface methods and
* adding context storage field
* <li>Injecting a Dynamic Class created from {@link VirtualFieldImplementationTemplate} to use
* injected field or fall back to a static map
* <li>Injecting a Dynamic Class created from {@link
* VirtualFieldImplementationsGenerator.VirtualFieldImplementationTemplate} to use injected
* field or fall back to a static map
* <li>Rewriting calls to the context-store to access the specific dynamic {@link
* VirtualFieldImplementationTemplate}
* VirtualFieldImplementationsGenerator.VirtualFieldImplementationTemplate}
* </ol>
*
* <p>Example:<br>
@ -78,52 +53,22 @@ import net.bytebuddy.utility.JavaModule;
* <em>FieldBackedImplementation$VirtualField$Runnable$RunnableState12345.getVirtualField(Runnable.class,
* RunnableState.class)</em>
*/
public class FieldBackedImplementationInstaller implements VirtualFieldImplementationInstaller {
public final class FieldBackedImplementationInstaller
implements VirtualFieldImplementationInstaller {
private static final TransformSafeLogger logger =
TransformSafeLogger.getLogger(FieldBackedImplementationInstaller.class);
/**
* Note: the value here has to be inside on of the prefixes in {@link
* io.opentelemetry.javaagent.tooling.Constants#BOOTSTRAP_PACKAGE_PREFIXES}. This ensures that
* 'isolating' (or 'module') classloaders like jboss and osgi see injected classes. This works
* because we instrument those classloaders to load everything inside bootstrap packages.
*/
private static final String DYNAMIC_CLASSES_PACKAGE =
"io.opentelemetry.javaagent.bootstrap.instrumentation.context.";
private static final String INSTALLED_FIELDS_MARKER_CLASS_NAME =
Utils.getInternalName(VirtualFieldInstalledMarker.class);
private static final Method FIND_VIRTUAL_FIELD_METHOD;
private static final Method FIND_VIRTUAL_FIELD_IMPL_METHOD;
static {
try {
FIND_VIRTUAL_FIELD_METHOD = VirtualField.class.getMethod("find", Class.class, Class.class);
FIND_VIRTUAL_FIELD_IMPL_METHOD =
VirtualFieldImplementationTemplate.class.getMethod(
"getVirtualField", Class.class, Class.class);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static final boolean FIELD_INJECTION_ENABLED =
Config.get().getBoolean("otel.javaagent.experimental.field-injection.enabled", true);
private final Class<?> instrumenterClass;
private final ByteBuddy byteBuddy;
private final VirtualFieldMappings virtualFieldMappings;
// fields-accessor-interface-name -> fields-accessor-interface-dynamic-type
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
private final FieldAccessorInterfaces fieldAccessorInterfaces;
private final AgentBuilder.Transformer fieldAccessorInterfacesInjector;
// context-store-type-name -> context-store-type-name-dynamic-type
private final Map<String, DynamicType.Unloaded<?>> virtualFieldImplementations;
private final VirtualFieldImplementations virtualFieldImplementations;
private final AgentBuilder.Transformer virtualFieldImplementationsInjector;
private final Instrumentation instrumentation;
@ -135,27 +80,17 @@ public class FieldBackedImplementationInstaller implements VirtualFieldImplement
// This class is used only when running with javaagent, thus this calls is safe
this.instrumentation = InstrumentationHolder.getInstrumentation();
byteBuddy = new ByteBuddy();
fieldAccessorInterfaces = generateFieldAccessorInterfaces();
fieldAccessorInterfacesInjector = bootstrapHelperInjector(fieldAccessorInterfaces.values());
virtualFieldImplementations = generateVirtualFieldImplementationClasses();
ByteBuddy byteBuddy = new ByteBuddy();
fieldAccessorInterfaces =
new FieldAccessorInterfacesGenerator(byteBuddy)
.generateFieldAccessorInterfaces(virtualFieldMappings);
fieldAccessorInterfacesInjector =
bootstrapHelperInjector(fieldAccessorInterfaces.getAllInterfaces());
virtualFieldImplementations =
new VirtualFieldImplementationsGenerator(byteBuddy)
.generateClasses(virtualFieldMappings, fieldAccessorInterfaces);
virtualFieldImplementationsInjector =
bootstrapHelperInjector(virtualFieldImplementations.values());
}
public static <Q extends K, K, C> VirtualField<Q, C> findVirtualField(
Class<K> type, Class<C> fieldType) {
try {
String virtualFieldImplClassName =
getVirtualFieldImplementationClassName(type.getName(), fieldType.getName());
Class<?> contextStoreClass = Class.forName(virtualFieldImplClassName, false, null);
Method method = contextStoreClass.getMethod("getVirtualField", Class.class, Class.class);
return (VirtualField<Q, C>) method.invoke(null, type, fieldType);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("VirtualField not found", exception);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
throw new IllegalStateException("Failed to get VirtualField", exception);
}
bootstrapHelperInjector(virtualFieldImplementations.getAllClasses());
}
@Override
@ -167,156 +102,15 @@ public class FieldBackedImplementationInstaller implements VirtualFieldImplement
* invokes appropriate storage implementation.
*/
builder =
builder.transform(getTransformerForAsmVisitor(getVirtualFieldFindsRewritingVisitor()));
builder.transform(
getTransformerForAsmVisitor(
new VirtualFieldFindRewriter(
instrumenterClass, virtualFieldMappings, virtualFieldImplementations)));
builder = injectHelpersIntoBootstrapClassloader(builder);
}
return builder;
}
private AsmVisitorWrapper getVirtualFieldFindsRewritingVisitor() {
return new AsmVisitorWrapper() {
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MethodVisitor(Opcodes.ASM7, mv) {
/** The most recent objects pushed to the stack. */
private final Object[] stack = {null, null};
/** Most recent instructions. */
private final int[] insnStack = {-1, -1, -1};
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
pushOpcode(opcode);
if (Utils.getInternalName(FIND_VIRTUAL_FIELD_METHOD.getDeclaringClass())
.equals(owner)
&& FIND_VIRTUAL_FIELD_METHOD.getName().equals(name)
&& Type.getMethodDescriptor(FIND_VIRTUAL_FIELD_METHOD).equals(descriptor)) {
logger.trace(
"Found VirtualField#find() access in {}", instrumenterClass.getName());
/*
The idea here is that the rest if this method visitor collects last three instructions in `insnStack`
variable. Once we get here we check if those last three instructions constitute call that looks like
`VirtualField.find(K.class, V.class)`. If it does the inside of this if rewrites it to call
dynamically injected context store implementation instead.
*/
if ((insnStack[0] == Opcodes.INVOKESTATIC
&& insnStack[1] == Opcodes.LDC
&& insnStack[2] == Opcodes.LDC)
&& (stack[0] instanceof Type && stack[1] instanceof Type)) {
String fieldTypeName = ((Type) stack[0]).getClassName();
String typeName = ((Type) stack[1]).getClassName();
TypeDescription virtualFieldImplementationClass =
getVirtualFieldImplementation(typeName, fieldTypeName);
if (logger.isTraceEnabled()) {
logger.trace(
"Rewriting VirtualField#find() for instrumenter {}: {} -> {}",
instrumenterClass.getName(),
typeName,
fieldTypeName);
}
if (virtualFieldImplementationClass == null) {
throw new IllegalStateException(
String.format(
"Incorrect VirtualField usage detected. Cannot find implementation for VirtualField<%s, %s>. Was that field registered in %s#registerMuzzleVirtualFields()?",
typeName, fieldTypeName, instrumenterClass.getName()));
}
if (!virtualFieldMappings.hasMapping(typeName, fieldTypeName)) {
throw new IllegalStateException(
String.format(
"Incorrect VirtualField usage detected. Cannot find mapping for VirtualField<%s, %s>. Was that field registered in %s#registerMuzzleVirtualFields()?",
typeName, fieldTypeName, instrumenterClass.getName()));
}
// stack: fieldType | type
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
virtualFieldImplementationClass.getInternalName(),
FIND_VIRTUAL_FIELD_IMPL_METHOD.getName(),
Type.getMethodDescriptor(FIND_VIRTUAL_FIELD_IMPL_METHOD),
/* isInterface= */ false);
return;
}
throw new IllegalStateException(
"Incorrect VirtualField usage detected. Type and field type must be class-literals. Example of correct usage: VirtualField.find(Runnable.class, RunnableContext.class)");
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
/** Tracking the most recently used opcodes to assert proper api usage. */
private void pushOpcode(int opcode) {
System.arraycopy(insnStack, 0, insnStack, 1, insnStack.length - 1);
insnStack[0] = opcode;
}
/**
* Tracking the most recently pushed objects on the stack to assert proper api usage.
*/
private void pushStack(Object o) {
System.arraycopy(stack, 0, stack, 1, stack.length - 1);
stack[0] = o;
}
@Override
public void visitInsn(int opcode) {
pushOpcode(opcode);
super.visitInsn(opcode);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
pushOpcode(opcode);
super.visitJumpInsn(opcode, label);
}
@Override
public void visitIntInsn(int opcode, int operand) {
pushOpcode(opcode);
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int var) {
pushOpcode(opcode);
pushStack(var);
super.visitVarInsn(opcode, var);
}
@Override
public void visitLdcInsn(Object value) {
pushOpcode(Opcodes.LDC);
pushStack(value);
super.visitLdcInsn(value);
}
};
}
};
}
};
}
private AgentBuilder.Identified.Extendable injectHelpersIntoBootstrapClassloader(
AgentBuilder.Identified.Extendable builder) {
/*
@ -375,7 +169,7 @@ public class FieldBackedImplementationInstaller implements VirtualFieldImplement
}
@Override
public AgentBuilder.Identified.Extendable installFields(
public AgentBuilder.Identified.Extendable injectFields(
AgentBuilder.Identified.Extendable builder) {
if (FIELD_INJECTION_ENABLED) {
@ -417,7 +211,8 @@ public class FieldBackedImplementationInstaller implements VirtualFieldImplement
builder =
builder.transform(
getTransformerForAsmVisitor(
getFieldInjectionVisitor(entry.getKey(), entry.getValue())));
new RealFieldInjector(
fieldAccessorInterfaces, entry.getKey(), entry.getValue())));
}
}
}
@ -425,606 +220,25 @@ public class FieldBackedImplementationInstaller implements VirtualFieldImplement
}
private static AgentBuilder.RawMatcher safeToInjectFieldsMatcher() {
return new AgentBuilder.RawMatcher() {
@Override
public boolean matches(
TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain) {
/*
* The idea here is that we can add fields if class is just being loaded
* (classBeingRedefined == null) and we have to add same fields again if class we added
* fields before is being transformed again. Note: here we assume that Class#getInterfaces()
* returns list of interfaces defined immediately on a given class, not inherited from its
* parents. It looks like current JVM implementation does exactly this but javadoc is not
* explicit about that.
*/
return classBeingRedefined == null
|| Arrays.asList(classBeingRedefined.getInterfaces())
.contains(VirtualFieldInstalledMarker.class);
}
return (typeDescription, classLoader, module, classBeingRedefined, protectionDomain) -> {
/*
* The idea here is that we can add fields if class is just being loaded
* (classBeingRedefined == null) and we have to add same fields again if class we added
* fields before is being transformed again. Note: here we assume that Class#getInterfaces()
* returns list of interfaces defined immediately on a given class, not inherited from its
* parents. It looks like current JVM implementation does exactly this but javadoc is not
* explicit about that.
*/
return classBeingRedefined == null
|| Arrays.asList(classBeingRedefined.getInterfaces())
.contains(VirtualFieldInstalledMarker.class);
};
}
private AsmVisitorWrapper getFieldInjectionVisitor(String typeName, String fieldTypeName) {
return new AsmVisitorWrapper() {
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
// We are using Object class name instead of fieldTypeName here because this gets
// injected onto Bootstrap classloader where context class may be unavailable
private final TypeDescription contextType =
new TypeDescription.ForLoadedType(Object.class);
private final String fieldName = getRealFieldName(typeName);
private final String getterMethodName = getRealGetterName(typeName);
private final String setterMethodName = getRealSetterName(typeName);
private final TypeDescription interfaceType =
getFieldAccessorInterface(typeName, fieldTypeName);
private boolean foundField = false;
private boolean foundGetter = false;
private boolean foundSetter = false;
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
if (interfaces == null) {
interfaces = new String[] {};
}
Set<String> set = new LinkedHashSet<>(Arrays.asList(interfaces));
set.add(INSTALLED_FIELDS_MARKER_CLASS_NAME);
set.add(interfaceType.getInternalName());
super.visit(version, access, name, signature, superName, set.toArray(new String[] {}));
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
if (name.equals(fieldName)) {
foundField = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals(getterMethodName)) {
foundGetter = true;
}
if (name.equals(setterMethodName)) {
foundSetter = true;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
// Checking only for field existence is not enough as libraries like CGLIB only copy
// public/protected methods and not fields (neither public nor private ones) when
// they enhance a class.
// For this reason we check separately for the field and for the two accessors.
if (!foundField) {
cv.visitField(
// Field should be transient to avoid being serialized with the object.
Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT | Opcodes.ACC_SYNTHETIC,
fieldName,
contextType.getDescriptor(),
null,
null);
}
if (!foundGetter) {
addGetter();
}
if (!foundSetter) {
addSetter();
}
super.visitEnd();
}
// just 'standard' getter implementation
private void addGetter() {
MethodVisitor mv = getAccessorMethodVisitor(getterMethodName);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentedType.getInternalName(),
fieldName,
contextType.getDescriptor());
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
// just 'standard' setter implementation
private void addSetter() {
MethodVisitor mv = getAccessorMethodVisitor(setterMethodName);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitFieldInsn(
Opcodes.PUTFIELD,
instrumentedType.getInternalName(),
fieldName,
contextType.getDescriptor());
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private MethodVisitor getAccessorMethodVisitor(String methodName) {
return cv.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
methodName,
Utils.getMethodDefinition(interfaceType, methodName).getDescriptor(),
null,
null);
}
};
}
};
}
private TypeDescription getVirtualFieldImplementation(
String keyClassName, String contextClassName) {
DynamicType.Unloaded<?> type =
virtualFieldImplementations.get(
getVirtualFieldImplementationClassName(keyClassName, contextClassName));
if (type == null) {
return null;
} else {
return type.getTypeDescription();
}
}
private Map<String, DynamicType.Unloaded<?>> generateVirtualFieldImplementationClasses() {
Map<String, DynamicType.Unloaded<?>> virtualFieldImplementations =
new HashMap<>(virtualFieldMappings.size());
for (Map.Entry<String, String> entry : virtualFieldMappings.entrySet()) {
DynamicType.Unloaded<?> type =
makeVirtualFieldImplementationClass(entry.getKey(), entry.getValue());
virtualFieldImplementations.put(type.getTypeDescription().getName(), type);
}
return Collections.unmodifiableMap(virtualFieldImplementations);
}
/**
* Generate an 'implementation' of a context store class for given key class name and context
* class name.
*
* @param keyClassName key class name
* @param contextClassName context class name
* @return unloaded dynamic type containing generated class
*/
private DynamicType.Unloaded<?> makeVirtualFieldImplementationClass(
String keyClassName, String contextClassName) {
return byteBuddy
.rebase(VirtualFieldImplementationTemplate.class)
.modifiers(Visibility.PUBLIC, TypeManifestation.FINAL, SyntheticState.SYNTHETIC)
.name(getVirtualFieldImplementationClassName(keyClassName, contextClassName))
.visit(getVirtualFieldImplementationVisitor(keyClassName, contextClassName))
.make();
}
/**
* Returns a visitor that 'fills in' missing methods into concrete implementation of {@link
* VirtualFieldImplementationTemplate} for given key class name and context class name.
*
* @param keyClassName key class name
* @param contextClassName context class name
* @return visitor that adds implementation for methods that need to be generated
*/
private AsmVisitorWrapper getVirtualFieldImplementationVisitor(
String keyClassName, String contextClassName) {
return new AsmVisitorWrapper() {
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
private final TypeDescription accessorInterface =
getFieldAccessorInterface(keyClassName, contextClassName);
private final String accessorInterfaceInternalName = accessorInterface.getInternalName();
private final String instrumentedTypeInternalName = instrumentedType.getInternalName();
private final boolean frames =
implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6);
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if ("realGet".equals(name)) {
generateRealGetMethod(name);
return null;
} else if ("realPut".equals(name)) {
generateRealPutMethod(name);
return null;
} else if ("realSynchronizeInstance".equals(name)) {
generateRealSynchronizeInstanceMethod(name);
return null;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
/**
* Provides implementation for {@code realGet} method that looks like below.
*
* <blockquote>
*
* <pre>
* private Object realGet(final Object key) {
* if (key instanceof $accessorInterfaceInternalName) {
* return (($accessorInterfaceInternalName) key).$getterName();
* } else {
* return mapGet(key);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealGetMethod(String name) {
String getterName = getRealGetterName(keyClassName);
Label elseLabel = new Label();
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, accessorInterfaceInternalName);
mv.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
accessorInterfaceInternalName,
getterName,
Utils.getMethodDefinition(accessorInterface, getterName).getDescriptor(),
/* isInterface= */ true);
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapGet",
Utils.getMethodDefinition(instrumentedType, "mapGet").getDescriptor(),
/* isInterface= */ false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Provides implementation for {@code realPut} method that looks like below.
*
* <blockquote>
*
* <pre>
* private void realPut(final Object key, final Object value) {
* if (key instanceof $accessorInterfaceInternalName) {
* (($accessorInterfaceInternalName) key).$setterName(value);
* } else {
* mapPut(key, value);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealPutMethod(String name) {
String setterName = getRealSetterName(keyClassName);
Label elseLabel = new Label();
Label endLabel = new Label();
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, accessorInterfaceInternalName);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
accessorInterfaceInternalName,
setterName,
Utils.getMethodDefinition(accessorInterface, setterName).getDescriptor(),
/* isInterface= */ true);
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapPut",
Utils.getMethodDefinition(instrumentedType, "mapPut").getDescriptor(),
/* isInterface= */ false);
mv.visitLabel(endLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Provides implementation for {@code realSynchronizeInstance} method that looks like
* below.
*
* <blockquote>
*
* <pre>
* private Object realSynchronizeInstance(final Object key) {
* if (key instanceof $accessorInterfaceInternalName) {
* return key;
* } else {
* return mapSynchronizeInstance(key);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealSynchronizeInstanceMethod(String name) {
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
Label elseLabel = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapSynchronizeInstance",
Utils.getMethodDefinition(instrumentedType, "mapSynchronizeInstance")
.getDescriptor(),
/* isInterface= */ false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private MethodVisitor getMethodVisitor(String methodName) {
return cv.visitMethod(
Opcodes.ACC_PRIVATE,
methodName,
Utils.getMethodDefinition(instrumentedType, methodName).getDescriptor(),
null,
null);
}
};
}
};
}
/**
* Template class used to generate the class that accesses stored context using either key
* instance's own injected field or global hash map if field is not available.
*/
// Called from generated code
@SuppressWarnings({"UnusedMethod", "UnusedVariable", "MethodCanBeStatic"})
private static final class VirtualFieldImplementationTemplate
extends VirtualField<Object, Object> {
private static final VirtualFieldImplementationTemplate INSTANCE =
new VirtualFieldImplementationTemplate(Cache.newBuilder().setWeakKeys().build());
private final Cache<Object, Object> map;
private VirtualFieldImplementationTemplate(Cache<Object, Object> map) {
this.map = map;
}
@Override
public Object get(Object object) {
return realGet(object);
}
@Override
public Object computeIfNull(Object object, Supplier<Object> fieldValueSupplier) {
Object existingContext = realGet(object);
if (null != existingContext) {
return existingContext;
}
synchronized (realSynchronizeInstance(object)) {
existingContext = realGet(object);
if (null != existingContext) {
return existingContext;
}
Object context = fieldValueSupplier.get();
realPut(object, context);
return context;
}
}
@Override
public void set(Object object, Object fieldValue) {
realPut(object, fieldValue);
}
private Object realGet(Object key) {
// to be generated
return null;
}
private void realPut(Object key, Object value) {
// to be generated
}
private Object realSynchronizeInstance(Object key) {
// to be generated
return null;
}
private Object mapGet(Object key) {
return map.get(key);
}
private void mapPut(Object key, Object value) {
if (value == null) {
map.remove(key);
} else {
map.put(key, value);
}
}
private Object mapSynchronizeInstance(Object key) {
return map;
}
public static VirtualField getVirtualField(Class keyClass, Class contextClass) {
// We do not actually check the keyClass here - but that should be fine since compiler would
// check things for us.
return INSTANCE;
}
}
private TypeDescription getFieldAccessorInterface(String keyClassName, String contextClassName) {
DynamicType.Unloaded<?> type =
fieldAccessorInterfaces.get(
getContextAccessorInterfaceName(keyClassName, contextClassName));
if (type == null) {
return null;
} else {
return type.getTypeDescription();
}
}
private Map<String, DynamicType.Unloaded<?>> generateFieldAccessorInterfaces() {
Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces =
new HashMap<>(virtualFieldMappings.size());
for (Map.Entry<String, String> entry : virtualFieldMappings.entrySet()) {
DynamicType.Unloaded<?> type = makeFieldAccessorInterface(entry.getKey(), entry.getValue());
fieldAccessorInterfaces.put(type.getTypeDescription().getName(), type);
}
return Collections.unmodifiableMap(fieldAccessorInterfaces);
}
/**
* Generate an interface that provides field accessor methods for given key class name and context
* class name.
*
* @param keyClassName key class name
* @param contextClassName context class name
* @return unloaded dynamic type containing generated interface
*/
private DynamicType.Unloaded<?> makeFieldAccessorInterface(
String keyClassName, String contextClassName) {
// We are using Object class name instead of contextClassName here because this gets injected
// onto Bootstrap classloader where context class may be unavailable
TypeDescription contextType = new TypeDescription.ForLoadedType(Object.class);
return byteBuddy
.makeInterface()
.merge(SyntheticState.SYNTHETIC)
.name(getContextAccessorInterfaceName(keyClassName, contextClassName))
.defineMethod(getRealGetterName(keyClassName), contextType, Visibility.PUBLIC)
.withoutCode()
.defineMethod(getRealSetterName(keyClassName), TypeDescription.VOID, Visibility.PUBLIC)
.withParameter(contextType, "value")
.withoutCode()
.make();
}
private static AgentBuilder.Transformer getTransformerForAsmVisitor(AsmVisitorWrapper visitor) {
return (builder, typeDescription, classLoader, module) -> builder.visit(visitor);
}
private static String getVirtualFieldImplementationClassName(
String keyClassName, String contextClassName) {
return DYNAMIC_CLASSES_PACKAGE
+ FieldBackedImplementationInstaller.class.getSimpleName()
+ "$VirtualField$"
+ Utils.convertToInnerClassName(keyClassName)
+ "$"
+ Utils.convertToInnerClassName(contextClassName);
}
private String getContextAccessorInterfaceName(String keyClassName, String contextClassName) {
return DYNAMIC_CLASSES_PACKAGE
+ getClass().getSimpleName()
+ "$VirtualFieldAccessor$"
+ Utils.convertToInnerClassName(keyClassName)
+ "$"
+ Utils.convertToInnerClassName(contextClassName);
}
private static String getRealFieldName(String keyClassName) {
return "__opentelemetryVirtualField$" + Utils.convertToInnerClassName(keyClassName);
}
private static String getRealGetterName(String keyClassName) {
return "get" + getRealFieldName(keyClassName);
}
private static String getRealSetterName(String key) {
return "set" + getRealFieldName(key);
}
// Originally found in AgentBuilder.Transformer.NoOp, but removed in 1.10.7
enum NoOpTransformer implements AgentBuilder.Transformer {
INSTANCE;

View File

@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import io.opentelemetry.javaagent.tooling.Utils;
final class GeneratedVirtualFieldNames {
/**
* Note: the value here has to be inside on of the prefixes in {@link
* io.opentelemetry.javaagent.tooling.Constants#BOOTSTRAP_PACKAGE_PREFIXES}. This ensures that
* 'isolating' (or 'module') classloaders like jboss and osgi see injected classes. This works
* because we instrument those classloaders to load everything inside bootstrap packages.
*/
static final String DYNAMIC_CLASSES_PACKAGE =
"io.opentelemetry.javaagent.bootstrap.instrumentation.context.";
private GeneratedVirtualFieldNames() {}
static String getVirtualFieldImplementationClassName(String typeName, String fieldTypeName) {
return DYNAMIC_CLASSES_PACKAGE
+ FieldBackedImplementationInstaller.class.getSimpleName()
+ "$VirtualField$"
+ Utils.convertToInnerClassName(typeName)
+ "$"
+ Utils.convertToInnerClassName(fieldTypeName);
}
static String getFieldAccessorInterfaceName(String typeName, String fieldTypeName) {
return DYNAMIC_CLASSES_PACKAGE
+ FieldBackedImplementationInstaller.class.getSimpleName()
+ "$VirtualFieldAccessor$"
+ Utils.convertToInnerClassName(typeName)
+ "$"
+ Utils.convertToInnerClassName(fieldTypeName);
}
static String getRealFieldName(String typeName) {
return "__opentelemetryVirtualField$" + Utils.convertToInnerClassName(typeName);
}
static String getRealGetterName(String typeName) {
return "get" + getRealFieldName(typeName);
}
static String getRealSetterName(String typeName) {
return "set" + getRealFieldName(typeName);
}
}

View File

@ -7,10 +7,9 @@ package io.opentelemetry.javaagent.tooling.field;
import net.bytebuddy.agent.builder.AgentBuilder.Identified.Extendable;
public class NoopVirtualFieldImplementationInstaller
implements VirtualFieldImplementationInstaller {
final class NoopVirtualFieldImplementationInstaller implements VirtualFieldImplementationInstaller {
public static final NoopVirtualFieldImplementationInstaller INSTANCE =
static final NoopVirtualFieldImplementationInstaller INSTANCE =
new NoopVirtualFieldImplementationInstaller();
private NoopVirtualFieldImplementationInstaller() {}
@ -21,7 +20,7 @@ public class NoopVirtualFieldImplementationInstaller
}
@Override
public Extendable installFields(Extendable builder) {
public Extendable injectFields(Extendable builder) {
return builder;
}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealFieldName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealGetterName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealSetterName;
import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
import io.opentelemetry.javaagent.tooling.Utils;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.pool.TypePool;
final class RealFieldInjector implements AsmVisitorWrapper {
private static final String INSTALLED_FIELDS_MARKER_CLASS_NAME =
Utils.getInternalName(VirtualFieldInstalledMarker.class);
private final FieldAccessorInterfaces fieldAccessorInterfaces;
private final String typeName;
private final String fieldTypeName;
RealFieldInjector(
FieldAccessorInterfaces fieldAccessorInterfaces, String typeName, String fieldTypeName) {
this.fieldAccessorInterfaces = fieldAccessorInterfaces;
this.typeName = typeName;
this.fieldTypeName = fieldTypeName;
}
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
// We are using Object class name instead of fieldTypeName here because this gets
// injected onto Bootstrap classloader where context class may be unavailable
private final TypeDescription fieldType = new TypeDescription.ForLoadedType(Object.class);
private final String fieldName = getRealFieldName(typeName);
private final String getterMethodName = getRealGetterName(typeName);
private final String setterMethodName = getRealSetterName(typeName);
private final TypeDescription interfaceType =
fieldAccessorInterfaces.find(typeName, fieldTypeName);
private boolean foundField = false;
private boolean foundGetter = false;
private boolean foundSetter = false;
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
if (interfaces == null) {
interfaces = new String[] {};
}
Set<String> set = new LinkedHashSet<>(Arrays.asList(interfaces));
set.add(INSTALLED_FIELDS_MARKER_CLASS_NAME);
set.add(interfaceType.getInternalName());
super.visit(version, access, name, signature, superName, set.toArray(new String[] {}));
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
if (name.equals(fieldName)) {
foundField = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals(getterMethodName)) {
foundGetter = true;
}
if (name.equals(setterMethodName)) {
foundSetter = true;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
// Checking only for field existence is not enough as libraries like CGLIB only copy
// public/protected methods and not fields (neither public nor private ones) when
// they enhance a class.
// For this reason we check separately for the field and for the two accessors.
if (!foundField) {
cv.visitField(
// Field should be transient to avoid being serialized with the object.
Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT | Opcodes.ACC_SYNTHETIC,
fieldName,
fieldType.getDescriptor(),
null,
null);
}
if (!foundGetter) {
addGetter();
}
if (!foundSetter) {
addSetter();
}
super.visitEnd();
}
// just 'standard' getter implementation
private void addGetter() {
MethodVisitor mv = getAccessorMethodVisitor(getterMethodName);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentedType.getInternalName(),
fieldName,
fieldType.getDescriptor());
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
// just 'standard' setter implementation
private void addSetter() {
MethodVisitor mv = getAccessorMethodVisitor(setterMethodName);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitFieldInsn(
Opcodes.PUTFIELD,
instrumentedType.getInternalName(),
fieldName,
fieldType.getDescriptor());
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private MethodVisitor getAccessorMethodVisitor(String methodName) {
return cv.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
methodName,
Utils.getMethodDefinition(interfaceType, methodName).getDescriptor(),
null,
null);
}
};
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getVirtualFieldImplementationClassName;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.instrumentation.api.internal.RuntimeVirtualFieldSupplier;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
final class RuntimeFieldBasedImplementationSupplier
implements RuntimeVirtualFieldSupplier.VirtualFieldSupplier {
@Override
public <U extends T, T, F> VirtualField<U, F> find(Class<T> type, Class<F> fieldType) {
try {
String virtualFieldImplClassName =
getVirtualFieldImplementationClassName(type.getName(), fieldType.getName());
Class<?> contextStoreClass = Class.forName(virtualFieldImplClassName, false, null);
Method method = contextStoreClass.getMethod("getVirtualField", Class.class, Class.class);
return (VirtualField<U, F>) method.invoke(null, type, fieldType);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("VirtualField not found", exception);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
throw new IllegalStateException("Failed to get VirtualField", exception);
}
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import java.lang.reflect.Method;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.pool.TypePool;
final class VirtualFieldFindRewriter implements AsmVisitorWrapper {
private static final TransformSafeLogger logger =
TransformSafeLogger.getLogger(VirtualFieldFindRewriter.class);
private static final Method FIND_VIRTUAL_FIELD_METHOD;
private static final Method FIND_VIRTUAL_FIELD_IMPL_METHOD;
static {
try {
FIND_VIRTUAL_FIELD_METHOD = VirtualField.class.getMethod("find", Class.class, Class.class);
FIND_VIRTUAL_FIELD_IMPL_METHOD =
VirtualFieldImplementationsGenerator.VirtualFieldImplementationTemplate.class.getMethod(
"getVirtualField", Class.class, Class.class);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private final Class<?> instrumentationModuleClass;
private final VirtualFieldMappings virtualFieldMappings;
private final VirtualFieldImplementations virtualFieldImplementations;
public VirtualFieldFindRewriter(
Class<?> instrumentationModuleClass,
VirtualFieldMappings virtualFieldMappings,
VirtualFieldImplementations virtualFieldImplementations) {
this.instrumentationModuleClass = instrumentationModuleClass;
this.virtualFieldMappings = virtualFieldMappings;
this.virtualFieldImplementations = virtualFieldImplementations;
}
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MethodVisitor(Opcodes.ASM7, mv) {
/** The most recent objects pushed to the stack. */
private final Object[] stack = {null, null};
/** Most recent instructions. */
private final int[] insnStack = {-1, -1, -1};
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
pushOpcode(opcode);
if (Utils.getInternalName(FIND_VIRTUAL_FIELD_METHOD.getDeclaringClass()).equals(owner)
&& FIND_VIRTUAL_FIELD_METHOD.getName().equals(name)
&& Type.getMethodDescriptor(FIND_VIRTUAL_FIELD_METHOD).equals(descriptor)) {
logger.trace(
"Found VirtualField#find() access in {}", instrumentationModuleClass.getName());
/*
The idea here is that the rest if this method visitor collects last three instructions in `insnStack`
variable. Once we get here we check if those last three instructions constitute call that looks like
`VirtualField.find(K.class, V.class)`. If it does the inside of this if rewrites it to call
dynamically injected context store implementation instead.
*/
if ((insnStack[0] == Opcodes.INVOKESTATIC
&& insnStack[1] == Opcodes.LDC
&& insnStack[2] == Opcodes.LDC)
&& (stack[0] instanceof Type && stack[1] instanceof Type)) {
String fieldTypeName = ((Type) stack[0]).getClassName();
String typeName = ((Type) stack[1]).getClassName();
TypeDescription virtualFieldImplementationClass =
virtualFieldImplementations.find(typeName, fieldTypeName);
if (logger.isTraceEnabled()) {
logger.trace(
"Rewriting VirtualField#find() for instrumenter {}: {} -> {}",
instrumentationModuleClass.getName(),
typeName,
fieldTypeName);
}
if (virtualFieldImplementationClass == null) {
throw new IllegalStateException(
String.format(
"Incorrect VirtualField usage detected. Cannot find implementation for VirtualField<%s, %s>. Was that field registered in %s#registerMuzzleVirtualFields()?",
typeName, fieldTypeName, instrumentationModuleClass.getName()));
}
if (!virtualFieldMappings.hasMapping(typeName, fieldTypeName)) {
throw new IllegalStateException(
String.format(
"Incorrect VirtualField usage detected. Cannot find mapping for VirtualField<%s, %s>. Was that field registered in %s#registerMuzzleVirtualFields()?",
typeName, fieldTypeName, instrumentationModuleClass.getName()));
}
// stack: fieldType | type
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
virtualFieldImplementationClass.getInternalName(),
FIND_VIRTUAL_FIELD_IMPL_METHOD.getName(),
Type.getMethodDescriptor(FIND_VIRTUAL_FIELD_IMPL_METHOD),
/* isInterface= */ false);
return;
}
throw new IllegalStateException(
"Incorrect VirtualField usage detected. Type and field type must be class-literals. Example of correct usage: VirtualField.find(Runnable.class, RunnableContext.class)");
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
/** Tracking the most recently used opcodes to assert proper api usage. */
private void pushOpcode(int opcode) {
System.arraycopy(insnStack, 0, insnStack, 1, insnStack.length - 1);
insnStack[0] = opcode;
}
/** Tracking the most recently pushed objects on the stack to assert proper api usage. */
private void pushStack(Object o) {
System.arraycopy(stack, 0, stack, 1, stack.length - 1);
stack[0] = o;
}
@Override
public void visitInsn(int opcode) {
pushOpcode(opcode);
super.visitInsn(opcode);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
pushOpcode(opcode);
super.visitJumpInsn(opcode, label);
}
@Override
public void visitIntInsn(int opcode, int operand) {
pushOpcode(opcode);
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int var) {
pushOpcode(opcode);
pushStack(var);
super.visitVarInsn(opcode, var);
}
@Override
public void visitLdcInsn(Object value) {
pushOpcode(Opcodes.LDC);
pushStack(value);
super.visitLdcInsn(value);
}
};
}
};
}
}

View File

@ -18,6 +18,6 @@ public interface VirtualFieldImplementationInstaller {
AgentBuilder.Identified.Extendable rewriteVirtualFieldsCalls(
AgentBuilder.Identified.Extendable builder);
/** Installs actual fields in classes referenced by {@link VirtualField} usages. */
AgentBuilder.Identified.Extendable installFields(AgentBuilder.Identified.Extendable builder);
/** Injects actual fields in classes referenced by {@link VirtualField} usages. */
AgentBuilder.Identified.Extendable injectFields(AgentBuilder.Identified.Extendable builder);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import io.opentelemetry.instrumentation.api.internal.RuntimeVirtualFieldSupplier;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappingsBuilderImpl;
public final class VirtualFieldImplementationInstallerFactory {
private static final TransformSafeLogger logger =
TransformSafeLogger.getLogger(VirtualFieldImplementationInstallerFactory.class);
public VirtualFieldImplementationInstallerFactory() {
RuntimeVirtualFieldSupplier.set(new RuntimeFieldBasedImplementationSupplier());
}
public VirtualFieldImplementationInstaller create(InstrumentationModule instrumentationModule) {
VirtualFieldMappingsBuilderImpl builder = new VirtualFieldMappingsBuilderImpl();
if (instrumentationModule instanceof InstrumentationModuleMuzzle) {
((InstrumentationModuleMuzzle) instrumentationModule).registerMuzzleVirtualFields(builder);
} else {
logger.debug(
"Found InstrumentationModule which does not implement InstrumentationModuleMuzzle: {}",
instrumentationModule);
}
VirtualFieldMappings mappings = builder.build();
return mappings.isEmpty()
? NoopVirtualFieldImplementationInstaller.INSTANCE
: new FieldBackedImplementationInstaller(instrumentationModule.getClass(), mappings);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getVirtualFieldImplementationClassName;
import java.util.Collection;
import java.util.Map;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
final class VirtualFieldImplementations {
// context-store-type-name -> context-store-type-name-dynamic-type
private final Map<String, DynamicType.Unloaded<?>> virtualFieldImplementations;
VirtualFieldImplementations(Map<String, DynamicType.Unloaded<?>> virtualFieldImplementations) {
this.virtualFieldImplementations = virtualFieldImplementations;
}
TypeDescription find(String typeName, String fieldTypeName) {
String virtualFieldImplementationClassName =
getVirtualFieldImplementationClassName(typeName, fieldTypeName);
DynamicType.Unloaded<?> type =
virtualFieldImplementations.get(virtualFieldImplementationClassName);
if (type == null) {
throw new IllegalStateException(
"Couldn't find VirtualField implementation class named "
+ virtualFieldImplementationClassName);
}
return type.getTypeDescription();
}
Collection<DynamicType.Unloaded<?>> getAllClasses() {
return virtualFieldImplementations.values();
}
}

View File

@ -0,0 +1,389 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.field;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealGetterName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealSetterName;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getVirtualFieldImplementationClassName;
import io.opentelemetry.instrumentation.api.caching.Cache;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.modifier.SyntheticState;
import net.bytebuddy.description.modifier.TypeManifestation;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.pool.TypePool;
final class VirtualFieldImplementationsGenerator {
private final ByteBuddy byteBuddy;
VirtualFieldImplementationsGenerator(ByteBuddy byteBuddy) {
this.byteBuddy = byteBuddy;
}
VirtualFieldImplementations generateClasses(
VirtualFieldMappings virtualFieldMappings, FieldAccessorInterfaces fieldAccessorInterfaces) {
Map<String, DynamicType.Unloaded<?>> virtualFieldImplementations =
new HashMap<>(virtualFieldMappings.size());
for (Map.Entry<String, String> entry : virtualFieldMappings.entrySet()) {
DynamicType.Unloaded<?> type =
makeVirtualFieldImplementationClass(
entry.getKey(), entry.getValue(), fieldAccessorInterfaces);
virtualFieldImplementations.put(type.getTypeDescription().getName(), type);
}
return new VirtualFieldImplementations(virtualFieldImplementations);
}
/**
* Generate an 'implementation' of a context store class for given key class name and context
* class name.
*
* @param typeName key class name
* @param fieldTypeName context class name
* @return unloaded dynamic type containing generated class
*/
private DynamicType.Unloaded<?> makeVirtualFieldImplementationClass(
String typeName, String fieldTypeName, FieldAccessorInterfaces fieldAccessorInterfaces) {
return byteBuddy
.rebase(VirtualFieldImplementationTemplate.class)
.modifiers(Visibility.PUBLIC, TypeManifestation.FINAL, SyntheticState.SYNTHETIC)
.name(getVirtualFieldImplementationClassName(typeName, fieldTypeName))
.visit(
getVirtualFieldImplementationVisitor(typeName, fieldTypeName, fieldAccessorInterfaces))
.make();
}
/**
* Returns a visitor that 'fills in' missing methods into concrete implementation of {@link
* VirtualFieldImplementationsGenerator.VirtualFieldImplementationTemplate} for given key class
* name and context class name.
*
* @param typeName key class name
* @param fieldTypeName context class name
* @return visitor that adds implementation for methods that need to be generated
*/
private AsmVisitorWrapper getVirtualFieldImplementationVisitor(
String typeName, String fieldTypeName, FieldAccessorInterfaces fieldAccessorInterfaces) {
return new AsmVisitorWrapper() {
@Override
public int mergeWriter(int flags) {
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
private final TypeDescription accessorInterface =
fieldAccessorInterfaces.find(typeName, fieldTypeName);
private final String accessorInterfaceInternalName = accessorInterface.getInternalName();
private final String instrumentedTypeInternalName = instrumentedType.getInternalName();
private final boolean frames =
implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6);
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if ("realGet".equals(name)) {
generateRealGetMethod(name);
return null;
} else if ("realPut".equals(name)) {
generateRealPutMethod(name);
return null;
} else if ("realSynchronizeInstance".equals(name)) {
generateRealSynchronizeInstanceMethod(name);
return null;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
/**
* Provides implementation for {@code realGet} method that looks like below.
*
* <blockquote>
*
* <pre>
* private Object realGet(final Object key) {
* if (key instanceof $accessorInterfaceInternalName) {
* return (($accessorInterfaceInternalName) key).$getterName();
* } else {
* return mapGet(key);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealGetMethod(String name) {
String getterName = getRealGetterName(typeName);
Label elseLabel = new Label();
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, accessorInterfaceInternalName);
mv.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
accessorInterfaceInternalName,
getterName,
Utils.getMethodDefinition(accessorInterface, getterName).getDescriptor(),
/* isInterface= */ true);
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapGet",
Utils.getMethodDefinition(instrumentedType, "mapGet").getDescriptor(),
/* isInterface= */ false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Provides implementation for {@code realPut} method that looks like below.
*
* <blockquote>
*
* <pre>
* private void realPut(final Object key, final Object value) {
* if (key instanceof $accessorInterfaceInternalName) {
* (($accessorInterfaceInternalName) key).$setterName(value);
* } else {
* mapPut(key, value);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealPutMethod(String name) {
String setterName = getRealSetterName(typeName);
Label elseLabel = new Label();
Label endLabel = new Label();
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, accessorInterfaceInternalName);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
accessorInterfaceInternalName,
setterName,
Utils.getMethodDefinition(accessorInterface, setterName).getDescriptor(),
/* isInterface= */ true);
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapPut",
Utils.getMethodDefinition(instrumentedType, "mapPut").getDescriptor(),
/* isInterface= */ false);
mv.visitLabel(endLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Provides implementation for {@code realSynchronizeInstance} method that looks like
* below.
*
* <blockquote>
*
* <pre>
* private Object realSynchronizeInstance(final Object key) {
* if (key instanceof $accessorInterfaceInternalName) {
* return key;
* } else {
* return mapSynchronizeInstance(key);
* }
* }
* </pre>
*
* </blockquote>
*
* @param name name of the method being visited
*/
private void generateRealSynchronizeInstanceMethod(String name) {
MethodVisitor mv = getMethodVisitor(name);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.INSTANCEOF, accessorInterfaceInternalName);
Label elseLabel = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(elseLabel);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
instrumentedTypeInternalName,
"mapSynchronizeInstance",
Utils.getMethodDefinition(instrumentedType, "mapSynchronizeInstance")
.getDescriptor(),
/* isInterface= */ false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private MethodVisitor getMethodVisitor(String methodName) {
return cv.visitMethod(
Opcodes.ACC_PRIVATE,
methodName,
Utils.getMethodDefinition(instrumentedType, methodName).getDescriptor(),
null,
null);
}
};
}
};
}
/**
* Template class used to generate the class that accesses stored context using either key
* instance's own injected field or global hash map if field is not available.
*/
// Called from generated code
@SuppressWarnings({"UnusedMethod", "UnusedVariable", "MethodCanBeStatic"})
static final class VirtualFieldImplementationTemplate extends VirtualField<Object, Object> {
private static final VirtualFieldImplementationTemplate INSTANCE =
new VirtualFieldImplementationTemplate(Cache.newBuilder().setWeakKeys().build());
private final Cache<Object, Object> map;
private VirtualFieldImplementationTemplate(Cache<Object, Object> map) {
this.map = map;
}
@Override
public Object get(Object object) {
return realGet(object);
}
@Override
public Object computeIfNull(Object object, Supplier<Object> fieldValueSupplier) {
Object existingContext = realGet(object);
if (null != existingContext) {
return existingContext;
}
synchronized (realSynchronizeInstance(object)) {
existingContext = realGet(object);
if (null != existingContext) {
return existingContext;
}
Object context = fieldValueSupplier.get();
realPut(object, context);
return context;
}
}
@Override
public void set(Object object, Object fieldValue) {
realPut(object, fieldValue);
}
private Object realGet(Object key) {
// to be generated
return null;
}
private void realPut(Object key, Object value) {
// to be generated
}
private Object realSynchronizeInstance(Object key) {
// to be generated
return null;
}
private Object mapGet(Object key) {
return map.get(key);
}
private void mapPut(Object key, Object value) {
if (value == null) {
map.remove(key);
} else {
map.put(key, value);
}
}
private Object mapSynchronizeInstance(Object key) {
return map;
}
public static VirtualField getVirtualField(Class keyClass, Class contextClass) {
// We do not actually check the keyClass here - but that should be fine since compiler would
// check things for us.
return INSTANCE;
}
}
}

View File

@ -10,22 +10,17 @@ import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import io.opentelemetry.instrumentation.api.internal.RuntimeVirtualFieldSupplier;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.tooling.HelperInjector;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
import io.opentelemetry.javaagent.tooling.field.FieldBackedImplementationInstaller;
import io.opentelemetry.javaagent.tooling.field.NoopVirtualFieldImplementationInstaller;
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstaller;
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstallerFactory;
import io.opentelemetry.javaagent.tooling.muzzle.HelperResourceBuilderImpl;
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle;
import io.opentelemetry.javaagent.tooling.muzzle.Mismatch;
import io.opentelemetry.javaagent.tooling.muzzle.ReferenceMatcher;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappingsBuilderImpl;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.List;
@ -42,13 +37,16 @@ public final class InstrumentationModuleInstaller {
private static final TransformSafeLogger logger =
TransformSafeLogger.getLogger(InstrumentationModule.class);
private static final Logger muzzleLogger = LoggerFactory.getLogger("muzzleMatcher");
private final Instrumentation instrumentation;
// Added here instead of AgentInstaller's ignores because it's relatively
// expensive. https://github.com/DataDog/dd-trace-java/pull/1045
public static final ElementMatcher.Junction<AnnotationSource> NOT_DECORATOR_MATCHER =
not(isAnnotatedWith(named("javax.decorator.Decorator")));
private final Instrumentation instrumentation;
private final VirtualFieldImplementationInstallerFactory virtualFieldInstallerFactory =
new VirtualFieldImplementationInstallerFactory();
public InstrumentationModuleInstaller(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
}
@ -85,7 +83,7 @@ public final class InstrumentationModuleInstaller {
Utils.getExtensionsClassLoader(),
instrumentation);
VirtualFieldImplementationInstaller contextProvider =
getVirtualFieldImplementationInstaller(instrumentationModule);
virtualFieldInstallerFactory.create(instrumentationModule);
AgentBuilder agentBuilder = parentAgentBuilder;
for (TypeInstrumentation typeInstrumentation : typeInstrumentations) {
@ -108,7 +106,7 @@ public final class InstrumentationModuleInstaller {
TypeTransformerImpl typeTransformer = new TypeTransformerImpl(extendableAgentBuilder);
typeInstrumentation.transform(typeTransformer);
extendableAgentBuilder = typeTransformer.getAgentBuilder();
extendableAgentBuilder = contextProvider.installFields(extendableAgentBuilder);
extendableAgentBuilder = contextProvider.injectFields(extendableAgentBuilder);
agentBuilder = extendableAgentBuilder;
}
@ -116,36 +114,6 @@ public final class InstrumentationModuleInstaller {
return agentBuilder;
}
private static VirtualFieldImplementationInstaller getVirtualFieldImplementationInstaller(
InstrumentationModule instrumentationModule) {
if (instrumentationModule instanceof InstrumentationModuleMuzzle) {
VirtualFieldMappingsBuilderImpl builder = new VirtualFieldMappingsBuilderImpl();
((InstrumentationModuleMuzzle) instrumentationModule).registerMuzzleVirtualFields(builder);
VirtualFieldMappings mappings = builder.build();
if (!mappings.isEmpty()) {
return FieldBackedProviderFactory.get(instrumentationModule.getClass(), mappings);
}
} else {
logger.debug(
"Found InstrumentationModule which does not implement InstrumentationModuleMuzzle: {}",
instrumentationModule);
}
return NoopVirtualFieldImplementationInstaller.INSTANCE;
}
private static class FieldBackedProviderFactory {
static {
RuntimeVirtualFieldSupplier.set(FieldBackedImplementationInstaller::findVirtualField);
}
static FieldBackedImplementationInstaller get(
Class<?> instrumenterClass, VirtualFieldMappings virtualFieldMappings) {
return new FieldBackedImplementationInstaller(instrumenterClass, virtualFieldMappings);
}
}
/**
* A ByteBuddy matcher that decides whether this instrumentation should be applied. Calls
* generated {@link ReferenceMatcher}: if any mismatch with the passed {@code classLoader} is