com.newrelic.agent.instrumentation.context.InstrumentationContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of newrelic-agent Show documentation
Show all versions of newrelic-agent Show documentation
The New Relic Java agent for full-stack observability
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.newrelic.agent.instrumentation.context;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.newrelic.agent.instrumentation.PointCut;
import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcher.Match;
import com.newrelic.agent.instrumentation.tracing.TraceClassVisitor;
import com.newrelic.agent.instrumentation.tracing.TraceDetails;
import com.newrelic.agent.util.asm.ClassResolver;
import com.newrelic.agent.util.asm.ClassResolvers;
import com.newrelic.weave.utils.WeaveUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Method;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class tracks information about a class passing through the {@link InstrumentationContextManager}. It keeps track
* of the methods of the class that have been matched by different class transformers that are registered with the
* manager.
*/
public class InstrumentationContext implements TraceDetailsList {
private static final TraceInformation EMPTY_TRACE_INFO = new TraceInformation();
protected final byte[] bytes;
private boolean modified;
private Multimap weavedMethods;
private Set timedMethods;
private Map oldReflectionStyleInstrumentationMethods;
private Map oldInvokerStyleInstrumentationMethods;
private TraceInformation tracedInfo;
private Map matches;
private String[] interfaces;
private Map bridgeMethods;
private String className;
private final Class classBeingRedefined;
private final ProtectionDomain protectionDomain;
private List classResolvers;
private boolean generated;
private boolean hasSource;
public InstrumentationContext(byte[] bytes, Class classBeingRedefined, ProtectionDomain protectionDomain) {
this.bytes = bytes;
this.classBeingRedefined = classBeingRedefined;
this.protectionDomain = protectionDomain;
}
public String[] getInterfaces() {
return null == interfaces ? new String[0] : interfaces;
}
public String getClassName() {
return className;
}
public Class getClassBeingRedefined() {
return classBeingRedefined;
}
public ProtectionDomain getProtectionDomain() {
return protectionDomain;
}
public void markAsModified() {
this.modified = true;
}
public boolean isModified() {
return modified;
}
public TraceInformation getTraceInformation() {
return tracedInfo == null ? EMPTY_TRACE_INFO : tracedInfo;
}
public boolean isTracerMatch() {
return (tracedInfo != null && tracedInfo.isMatch());
}
/**
* Adds a weaved method.
*
* @param instrumentationTitle The name of the instrumentation package from which the weaved code originated.
*/
public void addWeavedMethod(Method method, String instrumentationTitle) {
if (weavedMethods == null) {
weavedMethods = Multimaps.newSetMultimap(new HashMap>(),
new Supplier>() {
@Override
public Set get() {
return new HashSet<>();
}
});
}
weavedMethods.put(method, instrumentationTitle);
modified = true;
}
public PointCut getOldStylePointCut(Method method) {
PointCut pc = getOldInvokerStyleInstrumentationMethods().get(method);
if (pc == null) {
pc = getOldReflectionStyleInstrumentationMethods().get(method);
}
return pc;
}
private Map getOldInvokerStyleInstrumentationMethods() {
return oldInvokerStyleInstrumentationMethods == null ? Collections.emptyMap()
: oldInvokerStyleInstrumentationMethods;
}
private Map getOldReflectionStyleInstrumentationMethods() {
return oldReflectionStyleInstrumentationMethods == null ? Collections.emptyMap()
: oldReflectionStyleInstrumentationMethods;
}
public Set getWeavedMethods() {
return weavedMethods == null ? Collections.emptySet() : weavedMethods.keySet();
}
/**
* Returns methods that are timed with instrumentation injected by the new {@link TraceClassVisitor} or the old
* GenericClassAdapter.
*/
public Set getTimedMethods() {
return timedMethods == null ? Collections.emptySet() : timedMethods;
}
public Collection getMergeInstrumentationPackages(Method method) {
return weavedMethods == null ? Collections.emptySet() : weavedMethods.asMap().get(method);
}
public boolean isModified(Method method) {
return (getTimedMethods().contains(method)) || (getWeavedMethods().contains(method));
}
/**
* Adds methods that are timed with method tracers.
*/
public void addTimedMethods(Method... methods) {
if (timedMethods == null) {
timedMethods = new HashSet<>();
}
Collections.addAll(timedMethods, methods);
modified = true;
}
public void addOldReflectionStyleInstrumentationMethod(Method method, PointCut pointCut) {
if (oldReflectionStyleInstrumentationMethods == null) {
oldReflectionStyleInstrumentationMethods = new HashMap<>();
}
oldReflectionStyleInstrumentationMethods.put(method, pointCut);
modified = true;
}
public void addOldInvokerStyleInstrumentationMethod(Method method, PointCut pointCut) {
if (oldInvokerStyleInstrumentationMethods == null) {
oldInvokerStyleInstrumentationMethods = new HashMap<>();
}
oldInvokerStyleInstrumentationMethods.put(method, pointCut);
modified = true;
}
public Map getMatches() {
return matches == null ? Collections.emptyMap() : matches;
}
public void putTraceAnnotation(Method method, TraceDetails traceDetails) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.putTraceAnnotation(method, traceDetails);
}
public void addIgnoreApdexMethod(String methodName, String methodDesc) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.addIgnoreApdexMethod(methodName, methodDesc);
}
public void addIgnoreTransactionMethod(String methodName, String methodDesc) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.addIgnoreTransactionMethod(methodName, methodDesc);
}
public void addIgnoreTransactionMethod(Method m) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.addIgnoreTransactionMethod(m);
}
public void putMatch(ClassMatchVisitorFactory matcher, Match match) {
if (matches == null) {
matches = new HashMap<>();
}
matches.put(matcher, match);
}
public void setInterfaces(String[] interfaces) {
this.interfaces = interfaces;
}
public void setClassName(String className) {
this.className = className;
}
/**
* Adds methods to be traced (timed) by instrumentation injected by the {@link TraceClassVisitor}.
*/
public void addTracedMethods(Map tracedMethods) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.pullAll(tracedMethods);
}
/**
* Adds a method to be traced (timed) by instrumentation injected by the {@link TraceClassVisitor}.
*/
@Override
public void addTrace(Method method, TraceDetails traceDetails) {
if (tracedInfo == null) {
tracedInfo = new TraceInformation();
}
tracedInfo.putTraceAnnotation(method, traceDetails);
}
public void match(ClassLoader loader, Class classBeingRedefined, ClassReader reader,
Collection classVisitorFactories) {
ClassVisitor visitor = null;
for (ClassMatchVisitorFactory factory : classVisitorFactories) {
ClassVisitor nextVisitor = factory.newClassMatchVisitor(loader, classBeingRedefined, reader, visitor, this);
if (nextVisitor != null) {
visitor = nextVisitor;
}
}
if (visitor != null) {
reader.accept(visitor, ClassReader.SKIP_CODE);
if (bridgeMethods != null) {
// resolve bridge methods
resolveBridgeMethods(reader);
} else {
bridgeMethods = ImmutableMap.of();
}
}
}
/**
* {@link ClassMatchVisitorFactory} implementations add bridge methods that they've matched to the
* {@link #bridgeMethods} map. In that initial pass they just add the method but don't resolve the actual
* implementation. In a second pass we visit the code to resolve the signature of the actual implementation.
*
* For example, if a class implements the generic {@link List} interface and specifies that the type is
* {@link Integer}, the matchers will add the add(Object) method to our bridged method map, and this method will set
* the value to the add(Integer) method which implements the add method.
*
* @see Opcodes#ACC_BRIDGE
*/
private void resolveBridgeMethods(ClassReader reader) {
ClassVisitor visitor = new ClassVisitor(WeaveUtils.ASM_API_LEVEL) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
final Method method = new Method(name, desc);
if (bridgeMethods.containsKey(method)) {
return new MethodVisitor(WeaveUtils.ASM_API_LEVEL) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
bridgeMethods.put(method, new Method(name, desc));
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
}
return null;
}
};
reader.accept(visitor, ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES);
}
/**
* Add a bridged method to this context.
*
* @see Opcodes#ACC_BRIDGE
*/
public void addBridgeMethod(Method method) {
if (bridgeMethods == null) {
bridgeMethods = new HashMap<>();
}
bridgeMethods.put(method, method);
}
/**
* Returns a map of bridge methods. The key is the generic method definition from the matchers, and the value is the
* actual method implementation with generic types. For example, a class may implement List with a specific type
* of Person. The class will implement a method called add(Person) and the JVM will generate the bridge method
* add(Object) which will simply invoke add(Person). Generally speaking, we want to be able to create matchers that
* match the loosely typed version of the method (the signature that's usually a bridge method), but we want to
* instrument the typed version of the method because it can be invoked directly without passing through the bridge
* implementation.
*/
public Map getBridgeMethods() {
return bridgeMethods;
}
public boolean isUsingLegacyInstrumentation() {
return null != oldInvokerStyleInstrumentationMethods || null != oldReflectionStyleInstrumentationMethods;
}
public boolean hasModifiedClassStructure() {
return null != oldInvokerStyleInstrumentationMethods;
}
/**
* Adds a class resolver to the current context.
*/
public void addClassResolver(ClassResolver classResolver) {
if (this.classResolvers == null) {
this.classResolvers = new ArrayList<>();
}
this.classResolvers.add(classResolver);
}
/**
* Returns a class resolver that will delegate to the class resolvers added with
* {@link #addClassResolver(ClassResolver)}. If those fail to resolve the class the given classloader is used.
*
* @see ClassResolvers#getClassLoaderResolver(ClassLoader)
*/
public ClassResolver getClassResolver(ClassLoader loader) {
ClassResolver classResolver = ClassResolvers.getClassLoaderResolver(loader);
if (classResolvers != null) {
classResolvers.add(classResolver);
classResolver = ClassResolvers.getMultiResolver(classResolvers);
}
return classResolver;
}
public void setGenerated(boolean isGenerated) {
this.generated = isGenerated;
}
/**
* Return true if the GeneratedClassDetector identified this class as a generated class.
*/
public boolean isGenerated() {
return generated;
}
public void setSourceAttribute(boolean hasSource) {
this.hasSource = hasSource;
}
/**
* Return true if the GeneratedClassDetector found that this class has a source attribute. Java class files are not
* required to have a source attribute. When a class is created by a compiler, the source attribute generally
* contains the name of the source file. When a class is generated by a bytecode tool, the attribute may contain
* anything or may be absent.
*
* @return true if a source attribute was found on the class file.
*/
public boolean hasSourceAttribute() {
return hasSource;
}
public URL getCodeSourceLocation(){
if((protectionDomain == null) || (protectionDomain.getCodeSource() == null)) {
return null;
}
return protectionDomain.getCodeSource().getLocation();
}
}